-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tag Use Case #8
Merged
Tag Use Case #8
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
6b93e6e
add useDebounce
krfong916 845e5a0
update tag editor to use useCombobox hook edit type support for combobox
krfong916 0c9d2c0
fix onStateChange capitalization bug, add editor use case UI
krfong916 8d1b902
add spacing and screen-size standards for scss
krfong916 53bb3e0
implement loader, redo editor css, fix a11y bugs
krfong916 152a810
xed isopen state when user input changes, prevent default when user n…
krfong916 4d572e8
* fixed input isopen state when user input changes - our tests didn't…
krfong916 2d30eee
init useMultipleSelection, create generic types file
krfong916 41dc125
add keyboard tests for usemultiselect
krfong916 7166dd6
change keyboard navigation API for usemultiselect
krfong916 d4a79ee
implement all tests for usemultiselection
krfong916 0b1b344
fix combobox tests and cleanup type namespace
krfong916 a74ce35
move combobox folder
krfong916 5ffc4e7
implement tag use case, fix arrow navigation remove selectedItemListP…
krfong916 e033268
fix merge refs to include functions, this allows for composeable refs…
krfong916 96783d4
fix initial state, highlighted index
krfong916 67276b6
implement current item selection index and accessible navigation
krfong916 3b450a2
add cancel debounce callback and tests for tag editor
krfong916 655f397
add msw for mocking fetch and init tag tests, add event listeners for…
krfong916 aaded5a
add mock server setup for jest tests
krfong916 5e7e8b9
implement final tag editor tests
krfong916 d13e762
WIP: cursor position and highlighting selected items, implement creat…
krfong916 bad0890
fix keydown navigation for usemultislection, when we can focus a mult…
krfong916 9c6a1e9
WIP: question styling, implemented error-handling
krfong916 5434c43
implement focus for editor, button spacing and color, remove extran c…
krfong916 ff867ef
add link and editor bindings, refactor focus management for q compone…
krfong916 4e55f63
WIP: layout and styling, todo: spacing on large screens
krfong916 7d0d0b3
WIP: error handling, implement review and spacing
krfong916 f72f4ca
implement error handling, validate on change
krfong916 b9f7697
implement a11y for form and base tests, TODO: network error message
krfong916 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface ComponentProps<State, ActionAndChanges> { | ||
stateReducer?: (state: State, actionAndChanges: ActionAndChanges) => State; | ||
} | ||
|
||
export type ItemsList = Record<string, any>; |
33 changes: 33 additions & 0 deletions
33
...of-concepts/features/stories/useMultipleSelection/__tests__/useMultipleSelection.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
describe('useMultipleSelection hook', () => { | ||
/** | ||
* ********************* | ||
* | ||
* Keyboard Navigation | ||
* | ||
* ********************* | ||
*/ | ||
test('a left or up arrow on the highest-indexed element does nothing', () => {}); | ||
|
||
test('a right or down arrow on the lowest-indexed element places focus back on the textbox', () => {}); | ||
|
||
test('a left or up arrow navigates to higher-indexed elements', () => {}); | ||
|
||
test('a right or down arrow navigates to lower-indexed elements', () => {}); | ||
|
||
/** | ||
* ********************* | ||
* | ||
* Keyboard Interaction | ||
* | ||
* ********************* | ||
*/ | ||
test('enter or spacebar calls registered action', () => {}); | ||
|
||
/** | ||
* ********************* | ||
* | ||
* Accessibility | ||
* | ||
* ********************* | ||
*/ | ||
}); |
61 changes: 61 additions & 0 deletions
61
proof-of-concepts/features/stories/useMultipleSelection/reducer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { | ||
MultipleSelectionState, | ||
MultipleSelectionAction, | ||
MultipleSelectionStateChangeTypes | ||
} from './types'; | ||
|
||
export function multipleSelectionReducer<Item>( | ||
state: MultipleSelectionState<Item>, | ||
action: MultipleSelectionAction<Item> | ||
) { | ||
const { type } = action; | ||
const newState = { ...state }; | ||
switch (type) { | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_UP: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_DOWN: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_LEFT: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_RIGHT: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_ENTER: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_SPACEBAR: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.KEYDOWN_KEYDOWN_CLICK: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.MULTIPLE_SELECTION_GROUP_BLUR: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.MULTIPLE_SELECTION_GROUP_FOCUS: { | ||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.FUNCTION_ADD_SELECTED_ITEM: { | ||
if (action.item) return newState; | ||
const { item } = action; | ||
|
||
return newState; | ||
} | ||
case MultipleSelectionStateChangeTypes.FUNCTION_REMOVE_SELECTED_ITEM: { | ||
if (action.item) return newState; | ||
const { item } = action; | ||
newState.currentItems = newState.currentItems.filter( | ||
(current) => current.name !== item.name | ||
); | ||
return newState; | ||
} | ||
default: { | ||
throw new TypeError( | ||
`Unhandled action in useMultipleSelection. Received ${type}` | ||
); | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
proof-of-concepts/features/stories/useMultipleSelection/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { ComponentProps } from '../types'; | ||
export type MultipleSelectionProps<Item> = { | ||
initialItems?: Item[]; | ||
initialHighlightedIndex?: number; | ||
initialCurrentItem?: Item; | ||
onCurrentItemChange?: (item: Item) => {}; | ||
onHighlightedIndexChange?: (index: number) => {}; | ||
onRemoveSelectedItem?: (itemRemoved: Item) => {}; | ||
onAddSelectedItem?: (itemAdded: Item) => {}; | ||
onSelectedItemsEmpty?: () => {}; | ||
} & ComponentProps< | ||
MultipleSelectionState<Item>, | ||
MultipleSelectionActionAndChanges<Item> | ||
>; | ||
|
||
export type MultipleSelectionState<Item> = { | ||
currentItems: Item[]; | ||
currentItem: Item; | ||
highlightedIndex: number; | ||
hasSelectedItems: boolean; | ||
}; | ||
|
||
export enum MultipleSelectionStateChangeTypes { | ||
KEYDOWN_ARROW_UP = '[multiple_selection_arrow_up]', | ||
KEYDOWN_ARROW_DOWN = '[multiple_selection_arrow_down]', | ||
KEYDOWN_ARROW_LEFT = '[multiple_selection_arrow_left]', | ||
KEYDOWN_ARROW_RIGHT = '[multiple_selection_arrow_right]', | ||
KEYDOWN_ENTER = '[multiple_selection_keydown_enter]', | ||
KEYDOWN_SPACEBAR = '[multiple_selection_keydown_spacebar]', | ||
KEYDOWN_KEYDOWN_CLICK = '[multiple_selection_keydown_click]', | ||
MULTIPLE_SELECTION_GROUP_BLUR = '[multiple_selection_group_blur]', | ||
MULTIPLE_SELECTION_GROUP_FOCUS = '[multiple_selection_group_focus]', | ||
FUNCTION_ADD_SELECTED_ITEM = '[multiple_selection_function_add_seletected_item]', | ||
FUNCTION_REMOVE_SELECTED_ITEM = '[multiple_selection_function_remove_seletected_item]' | ||
} | ||
|
||
export type MultipleSelectionAction<Item> = { | ||
type: MultipleSelectionStateChangeTypes; | ||
item?: Partial<MultipleSelectionState<Item>>; | ||
}; | ||
|
||
export type MultipleSelectionActionAndChanges<Item> = { | ||
changes: MultipleSelectionState<Item>; | ||
action: MultipleSelectionAction<Item>; | ||
}; |
150 changes: 150 additions & 0 deletions
150
proof-of-concepts/features/stories/useMultipleSelection/useMultipleSelection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import React from 'react'; | ||
import { multipleSelectionReducer } from './reducer'; | ||
import { computeInitialState } from './utils'; | ||
import { | ||
normalizeKey, | ||
useControlledReducer, | ||
mergeRefs, | ||
callAllEventHandlers, | ||
noop | ||
} from '../utils'; | ||
import { | ||
MultipleSelectionProps, | ||
MultipleSelectionState, | ||
MultipleSelectionAction, | ||
MultipleSelectionStateChangeTypes, | ||
MultipleSelectionActionAndChanges | ||
} from './types'; | ||
|
||
export default function useMultipleSelection<Item>( | ||
props: MultipleSelectionProps<Item> | ||
) { | ||
const [state, dispatch] = useControlledReducer< | ||
( | ||
state: MultipleSelectionState<Item>, | ||
action: MultipleSelectionAction<Item> | ||
) => MultipleSelectionState<Item>, | ||
MultipleSelectionState<Item>, | ||
MultipleSelectionProps<Item>, | ||
MultipleSelectionStateChangeTypes, | ||
MultipleSelectionActionAndChanges<Item> | ||
>(multipleSelectionReducer, computeInitialState<Item>(props), props); | ||
|
||
const keydownHandlers = React.useCallback( | ||
(e: React.KeyboardEvent) => ({ | ||
Enter: () => { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.KEYDOWN_ENTER | ||
}); | ||
}, | ||
Spacebar: () => { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.KEYDOWN_SPACEBAR | ||
}); | ||
}, | ||
ArrowDown: () => { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_DOWN | ||
}); | ||
}, | ||
ArrowUp: () => { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_UP | ||
}); | ||
}, | ||
ArrowLeft: () => { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_LEFT | ||
}); | ||
}, | ||
ArrowRight: () => { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.KEYDOWN_ARROW_RIGHT | ||
}); | ||
} | ||
}), | ||
[dispatch, state] | ||
); | ||
|
||
function getSelectedItemProps() {} | ||
|
||
function getMultiSelectionLabelProps() {} | ||
|
||
function removeSelectedItem(item: Item) { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.FUNCTION_REMOVE_SELECTED_ITEM, | ||
item | ||
}); | ||
} | ||
|
||
function addSelectedItem(item: Item) { | ||
dispatch({ | ||
type: MultipleSelectionStateChangeTypes.FUNCTION_ADD_SELECTED_ITEM, | ||
item | ||
}); | ||
} | ||
|
||
return { | ||
getSelectedItemProps, | ||
getMultiSelectionLabelProps, | ||
removeSelectedItem, | ||
addSelectedItem | ||
}; | ||
} | ||
|
||
/* the internal data structure of selected items will function as a queue */ | ||
|
||
/* Usage */ | ||
// [] the user can assign interactions to the selected elements | ||
|
||
/* state */ | ||
// [] we want to have state of all currently selected items: Items[] | ||
// [] we want to store the currently highlighted/active element: Item | ||
// [] we want to have state of whether or not we have selected items: boolean based on undefined or defined list | ||
// [] we want to store the index of the currently highlighted/active element: number | ||
|
||
/* props */ | ||
// [x] when a new highlighted index occurs recommendations based on these state change types^ | ||
|
||
/* state change types - what does the user need? */ | ||
// [x] when the group is blurred | ||
// [x] when the group is active | ||
// [x] when an item is clicked | ||
// [x] when a keyboard left | ||
// [x] when a keyboard right | ||
// [x] when a keyboard up | ||
// [x] when a keyboard down happens | ||
// [x] when a keyboard enter | ||
// [x] when a keyboard spacebar | ||
|
||
/* UI */ | ||
// [] selected items: list | ||
// [] selected item: list element | ||
|
||
/* UX */ | ||
// [x] we want to have keyboard navigation | ||
// [] left up and down are the same | ||
// [x] we want to have keyboard interaction | ||
// [x] enter, spacebar | ||
|
||
/* functions.operations */ | ||
// [x] getSelectedItemProps | ||
// [] getLabelProps - associates/describes what the group belongs to | ||
|
||
// [x] onHighlightedIndexChange | ||
// [x] onHasSelectedItems | ||
|
||
// [] remove a selected item | ||
// [] the user specifies when/where the removal happens | ||
// [] we just provide a callback for them to use/specify in their code | ||
// [] add item to selected items | ||
// [] the user specifies when/where the addition happens | ||
// [] we just provide a callback for them to use/specify in their code | ||
|
||
/* accessibility */ | ||
// [] we want the currently active item to have a dialogue to let the user know what item is active, number of total items, and what group the selected items belong to | ||
// [] we want the selected item list to be associated with a parent/combobox | ||
// [] we want to tell the user how to interact with the item? | ||
|
||
/* questions */ | ||
// [] how to remove elements? |
37 changes: 37 additions & 0 deletions
37
proof-of-concepts/features/stories/useMultipleSelection/utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { MultipleSelectionProps, MultipleSelectionState } from './types'; | ||
import { capitalizeString } from '../utils'; | ||
|
||
export function computeInitialState<Item>( | ||
props: MultipleSelectionProps<Item> | ||
): MultipleSelectionState<Item> { | ||
getInitialValue; | ||
return {}; | ||
} | ||
|
||
/** | ||
* Use the keys of state to get the initial values of properties defined in props | ||
* Props and State share the same keys. | ||
*/ | ||
export function getInitialValue<Item>( | ||
props: MultipleSelectionProps<Item>, | ||
propKey: keyof MultipleSelectionState<Item> | ||
): Partial<MultipleSelectionState<Item>> { | ||
if (propKey in props) { | ||
return props[propKey as keyof MultipleSelectionProps<Item>] as Partial< | ||
MultipleSelectionState<Item> | ||
>; | ||
} | ||
|
||
// get the user-provided initial prop state, it is a piece of state | ||
const initialPropKey = `initial${capitalizeString( | ||
propKey | ||
)}` as keyof MultipleSelectionProps<Item>; | ||
if (initialPropKey in props) { | ||
// console.log('initialPropKey', initialPropKey); | ||
// console.log('initialPropKey', props[initialPropKey]); | ||
return props[initialPropKey] as Partial<MultipleSelectionState<Item>>; | ||
} | ||
|
||
// return values from statically defined initial state object | ||
return initialState[propKey] as Partial<MultipleSelectionState<Item>>; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The component accepts from initial props. Get those initial values if they exist