diff --git a/README.md b/README.md index eb0f265..a42a09e 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ the main Picker container component. | height | 216 | `number`
Height of the picker in `px` | | itemHeight | 36 | `number`
Height of each item (that is each option) in `px` | | wheelMode | `'off'` | `'off' \| 'natural' \| 'normal'`
Enable wheel scrolling on desktop browsers | +| showDivider | `true` | `boolean`
Whether to show the center divider lines | ### Picker.Column diff --git a/lib/components/Picker.tsx b/lib/components/Picker.tsx index 56c4b19..a19cae8 100644 --- a/lib/components/Picker.tsx +++ b/lib/components/Picker.tsx @@ -1,107 +1,126 @@ -import { CSSProperties, HTMLProps, MutableRefObject, createContext, useCallback, useContext, useMemo, useReducer } from 'react' - -const DEFAULT_HEIGHT = 216 -const DEFAULT_ITEM_HEIGHT = 36 -const DEFAULT_WHEEL_MODE = 'off' +import { + CSSProperties, + HTMLProps, + MutableRefObject, + createContext, + useCallback, + useContext, + useMemo, + useReducer, +} from "react"; + +const DEFAULT_HEIGHT = 216; +const DEFAULT_ITEM_HEIGHT = 36; +const DEFAULT_WHEEL_MODE = "off"; interface Option { - value: string | number - element: MutableRefObject + value: string | number; + element: MutableRefObject; } export interface PickerValue { - [key: string]: string | number + [key: string]: string | number; } -export interface PickerRootProps extends Omit, 'value' | 'onChange'> { - value: TType - onChange: (value: TType, key: string) => void - height?: number - itemHeight?: number - wheelMode?: 'off' | 'natural' | 'normal' +export interface PickerRootProps + extends Omit, "value" | "onChange"> { + value: TType; + onChange: (value: TType, key: string) => void; + height?: number; + itemHeight?: number; + wheelMode?: "off" | "natural" | "normal"; + showDivider?: boolean; } const PickerDataContext = createContext<{ - height: number - itemHeight: number - wheelMode: 'off' | 'natural' | 'normal' - value: PickerValue - optionGroups: { [key: string]: Option[] } -} | null>(null) -PickerDataContext.displayName = 'PickerDataContext' + height: number; + itemHeight: number; + wheelMode: "off" | "natural" | "normal"; + value: PickerValue; + optionGroups: { [key: string]: Option[] }; +} | null>(null); +PickerDataContext.displayName = "PickerDataContext"; export function usePickerData(componentName: string) { - const context = useContext(PickerDataContext) + const context = useContext(PickerDataContext); if (context === null) { - const error = new Error(`<${componentName} /> is missing a parent component.`) + const error = new Error( + `<${componentName} /> is missing a parent component.` + ); if (Error.captureStackTrace) { - Error.captureStackTrace(error, usePickerData) + Error.captureStackTrace(error, usePickerData); } - throw error + throw error; } - return context + return context; } const PickerActionsContext = createContext<{ - registerOption(key: string, option: Option): () => void - change(key: string, value: string | number): boolean -} | null>(null) -PickerActionsContext.displayName = 'PickerActionsContext' + registerOption(key: string, option: Option): () => void; + change(key: string, value: string | number): boolean; +} | null>(null); +PickerActionsContext.displayName = "PickerActionsContext"; export function usePickerActions(componentName: string) { - const context = useContext(PickerActionsContext) + const context = useContext(PickerActionsContext); if (context === null) { - const error = new Error(`<${componentName} /> is missing a parent component.`) + const error = new Error( + `<${componentName} /> is missing a parent component.` + ); if (Error.captureStackTrace) { - Error.captureStackTrace(error, usePickerActions) + Error.captureStackTrace(error, usePickerActions); } - throw error + throw error; } - return context + return context; } function sortByDomNode( nodes: T[], - resolveKey: (item: T) => HTMLElement | null = (i) => i as unknown as HTMLElement | null + resolveKey: (item: T) => HTMLElement | null = (i) => + i as unknown as HTMLElement | null ): T[] { return nodes.slice().sort((aItem, zItem) => { - const a = resolveKey(aItem) - const z = resolveKey(zItem) + const a = resolveKey(aItem); + const z = resolveKey(zItem); - if (a === null || z === null) return 0 + if (a === null || z === null) return 0; - const position = a.compareDocumentPosition(z) + const position = a.compareDocumentPosition(z); - if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1 - if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1 - return 0 - }) + if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1; + if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1; + return 0; + }); } function pickerReducer( optionGroups: { [key: string]: Option[] }, action: { - type: 'REGISTER_OPTION' | 'UNREGISTER_OPTION' - key: string - option: Option + type: "REGISTER_OPTION" | "UNREGISTER_OPTION"; + key: string; + option: Option; } ) { switch (action.type) { - case 'REGISTER_OPTION': { - const { key, option } = action - let nextOptionsForKey = [...(optionGroups[key] || []), option] - nextOptionsForKey = sortByDomNode(nextOptionsForKey, (o) => o.element.current) + case "REGISTER_OPTION": { + const { key, option } = action; + let nextOptionsForKey = [...(optionGroups[key] || []), option]; + nextOptionsForKey = sortByDomNode( + nextOptionsForKey, + (o) => o.element.current + ); return { ...optionGroups, [key]: nextOptionsForKey, - } + }; } - case 'UNREGISTER_OPTION': { - const { key, option } = action + case "UNREGISTER_OPTION": { + const { key, option } = action; return { ...optionGroups, [key]: (optionGroups[key] || []).filter((o) => o !== option), - } + }; } default: { throw Error(`Unknown action: ${action.type as string}`); @@ -118,55 +137,61 @@ function PickerRoot(props: PickerRootProps) { height = DEFAULT_HEIGHT, itemHeight = DEFAULT_ITEM_HEIGHT, wheelMode = DEFAULT_WHEEL_MODE, + showDivider = true, ...restProps - } = props + } = props; const highlightStyle = useMemo( () => ({ height: itemHeight, marginTop: -(itemHeight / 2), - position: 'absolute', - top: '50%', + position: "absolute", + top: "50%", left: 0, - width: '100%', - pointerEvents: 'none', + width: "100%", + pointerEvents: "none", }), [itemHeight] - ) + ); const containerStyle = useMemo( () => ({ height: `${height}px`, - position: 'relative', - display: 'flex', - justifyContent: 'center', - overflow: 'hidden', - maskImage: 'linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent)', - WebkitMaskImage: 'linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent)', + position: "relative", + display: "flex", + justifyContent: "center", + overflow: "hidden", + maskImage: + "linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent)", + WebkitMaskImage: + "linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent)", }), [height] - ) + ); - const [optionGroups, dispatch] = useReducer(pickerReducer, {}) + const [optionGroups, dispatch] = useReducer(pickerReducer, {}); const pickerData = useMemo( () => ({ height, itemHeight, wheelMode, value, optionGroups }), [height, itemHeight, value, optionGroups, wheelMode] - ) - - const triggerChange = useCallback((key: string, nextValue: string) => { - if (value[key] === nextValue) return false - const nextPickerValue = { ...value, [key]: nextValue } - onChange(nextPickerValue, key) - return true - }, [onChange, value]) + ); + + const triggerChange = useCallback( + (key: string, nextValue: string) => { + if (value[key] === nextValue) return false; + const nextPickerValue = { ...value, [key]: nextValue }; + onChange(nextPickerValue, key); + return true; + }, + [onChange, value] + ); const registerOption = useCallback((key: string, option: Option) => { - dispatch({ type: 'REGISTER_OPTION', key, option }) - return () => dispatch({ type: 'UNREGISTER_OPTION', key, option }) - }, []) + dispatch({ type: "REGISTER_OPTION", key, option }); + return () => dispatch({ type: "UNREGISTER_OPTION", key, option }); + }, []); const pickerActions = useMemo( () => ({ registerOption, change: triggerChange }), [registerOption, triggerChange] - ) + ); return (
(props: PickerRootProps) { {children} -
-
-
-
+ {showDivider && ( +
+
+
+
+ )}
- ) + ); } -export default PickerRoot +export default PickerRoot;