From 1ea0819170db32996c7f44a2ffd46ab79c112cb0 Mon Sep 17 00:00:00 2001 From: Harry Hogg Date: Sun, 10 Dec 2023 23:57:25 +0000 Subject: [PATCH] fix(ConfigMenu): Change prop to array value, fixed generics, action menu and added foramtter to oneOf and manyOf entries --- .../package/src/ConfigMenu/ConfigMenu.tsx | 128 +++++++++--------- .../package/src/ConfigMenu/MenuItemAction.tsx | 2 - workspaces/package/src/index.ts | 6 + .../site/src/docs/catalog/ConfigMenu.tsx | 58 +++++--- 4 files changed, 115 insertions(+), 79 deletions(-) diff --git a/workspaces/package/src/ConfigMenu/ConfigMenu.tsx b/workspaces/package/src/ConfigMenu/ConfigMenu.tsx index ee63b864..1d2fa866 100644 --- a/workspaces/package/src/ConfigMenu/ConfigMenu.tsx +++ b/workspaces/package/src/ConfigMenu/ConfigMenu.tsx @@ -1,5 +1,5 @@ import { LucideIcon } from 'lucide-react'; -import { PropsWithChildren, useState } from 'react'; +import { Fragment, PropsWithChildren, useState } from 'react'; import { TransitionBox, TransitionBoxProps, @@ -12,22 +12,22 @@ import MenuItemNavigate from './MenuItemNavigate'; export interface ConfigMenuProps extends TransitionBoxProps { config: MenuConfig; } -export type MenuConfig = Record; +export type MenuConfig = MenuConfigEntry[]; -export type ConfigEntry = { +export type MenuConfigEntry = { label: string; icon: LucideIcon; - config: ConfigEntryValue; + config: T; }; -export type ConfigEntryValue = - | ConfigBoolean - | ConfigNumber - | ConfigOneOf - | ConfigManyOf - | ConfigAction; +export type MenuConfigEntryValue = + | MenuConfigBoolean + | MenuConfigNumber + | MenuConfigOneOf + | MenuConfigManyOf + | MenuConfigAction; -export type ConfigBoolean = { +export type MenuConfigBoolean = { type: 'boolean'; value: boolean; labelTrue: string; @@ -35,60 +35,63 @@ export type ConfigBoolean = { onChange: (value: boolean) => void; }; -export type ConfigNumber = { +export type MenuConfigNumber = { type: 'number'; value: number; min: number; max: number; step: number; - formatter: (value: number) => string; + formatter?: (value: number) => string; onChange: (value: number) => void; }; -export type ConfigOneOf = { +export type MenuConfigOneOf = { type: 'oneOf'; - value: string; - options: string[]; - onChange: (value: string) => void; + value: T; + options: T[]; + formatter?: (value: T) => string; + onChange: (value: T) => void; }; -export type ConfigManyOf = { +export type MenuConfigManyOf = { type: 'manyOf'; - value: string[]; - options: string[]; - onChange: (value: string[]) => void; + value: T[]; + options: T[]; + formatter?: (value: T) => string; + onChange: (value: T[]) => void; }; -export type ConfigAction = { +export type MenuConfigAction = { type: 'action'; - label: string; - action: () => void; + onAction: () => void; }; -const isBoolean = (value: ConfigEntryValue): value is ConfigBoolean => +const isBoolean = (value: MenuConfigEntryValue): value is MenuConfigBoolean => value.type === 'boolean'; -const isNumber = (value: ConfigEntryValue): value is ConfigNumber => +const isNumber = (value: MenuConfigEntryValue): value is MenuConfigNumber => value.type === 'number'; -const isOneOf = (value: ConfigEntryValue): value is ConfigOneOf => +const isOneOf = (value: MenuConfigEntryValue): value is MenuConfigOneOf => value.type === 'oneOf'; -const isManyOf = (value: ConfigEntryValue): value is ConfigManyOf => - value.type === 'manyOf'; +const isManyOf = ( + value: MenuConfigEntryValue +): value is MenuConfigManyOf => value.type === 'manyOf'; -const isAction = (value: ConfigEntryValue): value is ConfigAction => +const isAction = (value: MenuConfigEntryValue): value is MenuConfigAction => value.type === 'action'; -const getLabel = (entry: ConfigEntry) => { +const getLabel = (entry: MenuConfigEntry) => { switch (entry.config.type) { case 'boolean': return entry.config.value ? entry.config.labelTrue : entry.config.labelFalse; case 'number': + return entry.config.formatter?.(entry.config.value) ?? entry.config.value; case 'oneOf': - return entry.config.value; + return entry.config.formatter?.(entry.config.value) ?? entry.config.value; case 'manyOf': if (entry.config.value.length === 0) { return 'None'; @@ -98,7 +101,7 @@ const getLabel = (entry: ConfigEntry) => { return 'All'; } case 'action': - return entry.config.label; + return entry.label; } }; @@ -117,19 +120,20 @@ export const ConfigMenu = ({ ...rest }: PropsWithChildren) => { const [activeKey, setActiveKey] = useState(__root); - const activeEntry = activeKey === __root ? undefined : config[activeKey]; + const activeIndex = config.findIndex((entry) => entry.label === activeKey); + const activeEntry = activeKey === __root ? undefined : config[activeIndex]; const createUpdateHandler = ( - entry: ConfigEntry, - value: Exclude['value'] + entry: MenuConfigEntry, + value: Exclude['value'] ) => () => { if (!isAction(entry.config)) { // Todo: Why as needed here? ( entry.config.onChange as ( - v: Exclude['value'] + v: Exclude['value'] ) => void )(value); } @@ -152,14 +156,29 @@ export const ConfigMenu = ({ > {activeKey === __root && ( - {Object.keys(config).map((key) => ( - setActiveKey(key)} - title={config[key].label} - value={getLabel(config[key])} - /> + {config.map((entry) => ( + + {isAction(entry.config) ? ( + { + event.stopPropagation(); + + if (isAction(entry.config)) { + entry.config.onAction(); + } + }} + /> + ) : ( + setActiveKey(entry.label)} + title={entry.label} + value={getLabel(entry)} + /> + )} + ))} )} @@ -198,7 +217,7 @@ export const ConfigMenu = ({ key={i} onClick={createUpdateHandler(activeEntry, i)} > - {activeEntry.config.formatter(i)} + {activeEntry.config.formatter?.(i) ?? i} ) )} @@ -212,7 +231,7 @@ export const ConfigMenu = ({ key={option} onClick={createUpdateHandler(activeEntry, option)} > - {option} + {activeEntry.config.formatter?.(option) ?? option} ) )} @@ -229,23 +248,10 @@ export const ConfigMenu = ({ toggleValueInArray(activeEntry.config.value, option) )} > - {option} + {activeEntry.config.formatter?.(option) ?? option} ) )} - - {isAction(activeEntry.config) && ( - { - if (isAction(activeEntry.config)) { - activeEntry.config.action(); - setActiveKey(__root); - } - }} - /> - )} )} diff --git a/workspaces/package/src/ConfigMenu/MenuItemAction.tsx b/workspaces/package/src/ConfigMenu/MenuItemAction.tsx index 3977de7d..47049879 100644 --- a/workspaces/package/src/ConfigMenu/MenuItemAction.tsx +++ b/workspaces/package/src/ConfigMenu/MenuItemAction.tsx @@ -22,8 +22,6 @@ export default function MenuItemAction({ {title} - - {/* */} ); } diff --git a/workspaces/package/src/index.ts b/workspaces/package/src/index.ts index 1094c7fa..7bb947a8 100644 --- a/workspaces/package/src/index.ts +++ b/workspaces/package/src/index.ts @@ -32,6 +32,12 @@ export { ConfigMenu, type ConfigMenuProps, type MenuConfig, + type MenuConfigEntry, + type MenuConfigBoolean, + type MenuConfigNumber, + type MenuConfigOneOf, + type MenuConfigManyOf, + type MenuConfigAction, } from './ConfigMenu/ConfigMenu'; export { DatePicker, type DatePickerProps } from './DatePicker/DatePicker'; export { Form, type FormProps } from './Form/Form'; diff --git a/workspaces/site/src/docs/catalog/ConfigMenu.tsx b/workspaces/site/src/docs/catalog/ConfigMenu.tsx index 0563167f..e11512e8 100644 --- a/workspaces/site/src/docs/catalog/ConfigMenu.tsx +++ b/workspaces/site/src/docs/catalog/ConfigMenu.tsx @@ -1,4 +1,10 @@ -import { BugIcon, GaugeIcon, HighlighterIcon, PaletteIcon } from 'lucide-react'; +import { + BugIcon, + GaugeIcon, + HighlighterIcon, + PaletteIcon, + SaveIcon, +} from 'lucide-react'; import { ConfigMenu, ConfigMenuProps, MenuConfig } from 'preshape'; import { Fragment, useState } from 'react'; import { CatalogueItem } from '..'; @@ -20,7 +26,7 @@ const Item: CatalogueItem<{ showcase: { state: { ConfigMenu: { - config: {}, + config: [], }, }, Component: (props) => { @@ -32,8 +38,8 @@ const Item: CatalogueItem<{ ]); const [mode, setMode] = useState('Fill'); - const config: MenuConfig = { - speed: { + const config: MenuConfig = [ + { label: 'Speed', icon: GaugeIcon, config: { @@ -46,7 +52,7 @@ const Item: CatalogueItem<{ onChange: setSpeed, }, }, - debug: { + { label: 'Debug', icon: BugIcon, config: { @@ -57,17 +63,18 @@ const Item: CatalogueItem<{ onChange: setDebug, }, }, - annotations: { + { label: 'Annotations', icon: HighlighterIcon, config: { type: 'manyOf', value: annotations, - options: ['Axis origin', 'Transform', 'Vertex type'], + options: ['Axis_origin', 'Transform', 'Vertex_type'], onChange: setAnnotations, + formatter: (value) => value.replace('_', ' '), }, }, - mode: { + { label: 'Mode', icon: PaletteIcon, config: { @@ -75,9 +82,18 @@ const Item: CatalogueItem<{ value: mode, options: ['Draw', 'Fill', 'View'], onChange: setMode, + formatter: (value) => value.replace('_', ' '), }, }, - }; + { + label: 'Save', + icon: SaveIcon, + config: { + type: 'action', + onAction: () => {}, + }, + }, + ]; return ( @@ -89,8 +105,8 @@ const Item: CatalogueItem<{ import { ConfigMenu } from 'preshape'; value.replace('_', ' '), }, }, - mode: { + { label: 'Mode', icon: PaletteIcon, config: { @@ -132,9 +149,18 @@ import { ConfigMenu } from 'preshape'; value: mode, options: ['Draw', 'Fill', 'View'], onChange: setMode, + formatter: (value) => value.replace('_', ' '), + }, + }, + { + label: 'Save', + icon: SaveIcon, + config: { + type: 'action', + onAction: () => {}, }, }, - }} + ]} /> `, },