From db4fa1d08c80a90b05352bd4ec2e53b0084f843f Mon Sep 17 00:00:00 2001 From: Sebastian Sebald Date: Tue, 23 Jul 2024 09:29:21 +0200 Subject: [PATCH] docs: Introduce an appearance demo (#4028) * improve that thing * that thing too * remove stuff * extract appearance getter * add types * :shrug: * styling yay * save * default list box * ! * style the floating default select * finally done with styling! * eeeeh * remove relative * remove log * vertical! * better * increase card border color * props to demo * styling * message * Create many-steaks-shave.md * fixes --- .changeset/many-steaks-shave.md | 8 ++ docs/content/__internal__/index.mdx | 61 +++++++++- .../form/button/button-appearance.demo.tsx | 4 + .../content/components/form/button/button.mdx | 2 + docs/lib/utils.tsx | 39 ++++++ docs/ui/AppearanceDemo.tsx | 112 +++++++++++++++++- docs/ui/AppearanceTable.tsx | 30 +---- docs/ui/ComponentDemo.tsx | 8 +- docs/ui/ThemeSwitch.tsx | 22 ++-- docs/ui/mdx.tsx | 8 +- .../src/components/ListBox.styles.ts | 2 +- .../theme-docs/src/components/Card.styles.ts | 5 +- .../theme-docs/src/components/Field.styles.ts | 21 ++++ .../src/components/HelpText.styles.ts | 6 + .../theme-docs/src/components/Input.styles.ts | 28 +++++ .../theme-docs/src/components/Label.styles.ts | 38 ++++++ .../src/components/ListBox.styles.ts | 33 ++++++ .../src/components/Select.styles.ts | 57 +++++++++ themes/theme-docs/src/components/index.ts | 6 + 19 files changed, 443 insertions(+), 47 deletions(-) create mode 100644 .changeset/many-steaks-shave.md create mode 100644 docs/content/components/form/button/button-appearance.demo.tsx create mode 100644 themes/theme-docs/src/components/Field.styles.ts create mode 100644 themes/theme-docs/src/components/HelpText.styles.ts create mode 100644 themes/theme-docs/src/components/Input.styles.ts create mode 100644 themes/theme-docs/src/components/Label.styles.ts create mode 100644 themes/theme-docs/src/components/ListBox.styles.ts create mode 100644 themes/theme-docs/src/components/Select.styles.ts diff --git a/.changeset/many-steaks-shave.md b/.changeset/many-steaks-shave.md new file mode 100644 index 0000000000..aed20fe0dc --- /dev/null +++ b/.changeset/many-steaks-shave.md @@ -0,0 +1,8 @@ +--- +"@marigold/docs": patch +"@marigold/components": patch +"@marigold/theme-b2b": patch +"@marigold/theme-docs": patch +--- + +docs: Introduce an appearance demo diff --git a/docs/content/__internal__/index.mdx b/docs/content/__internal__/index.mdx index 824ddf7681..ac96f3c4e9 100644 --- a/docs/content/__internal__/index.mdx +++ b/docs/content/__internal__/index.mdx @@ -59,6 +59,61 @@ caption: A page to test stuff -- foo -- bar -- baz +## Form Controls + + + + + + + +### Floating + +This is not meant to be used within a regular form. The floating form fields should be used when position over other content (e.g. demos). Be aware that your label will be suffixed with a ":". + + + + + + diff --git a/docs/content/components/form/button/button-appearance.demo.tsx b/docs/content/components/form/button/button-appearance.demo.tsx new file mode 100644 index 0000000000..5567ac78dc --- /dev/null +++ b/docs/content/components/form/button/button-appearance.demo.tsx @@ -0,0 +1,4 @@ +import { ButtonProps } from '@marigold/components'; +import { Button } from '@marigold/components'; + +export default (props: ButtonProps) => ; diff --git a/docs/content/components/form/button/button.mdx b/docs/content/components/form/button/button.mdx index ae80a8c208..9013588584 100644 --- a/docs/content/components/form/button/button.mdx +++ b/docs/content/components/form/button/button.mdx @@ -16,6 +16,8 @@ import { Button } from '@marigold/components'; ## Appearance + + ## Props diff --git a/docs/lib/utils.tsx b/docs/lib/utils.tsx index 5f8655fa7d..55de9264c0 100644 --- a/docs/lib/utils.tsx +++ b/docs/lib/utils.tsx @@ -1,3 +1,5 @@ +import type { ConfigSchema, Theme } from '@marigold/system'; + interface NestedStringObject { [key: string]: NestedStringObject | string; } @@ -16,3 +18,40 @@ export const iterateTokens = (colors: NestedStringObject, prefix = '') => { } return list; }; + +const getKeys = (schema: ConfigSchema) => { + return { + variant: schema?.variant && Object.keys(schema?.variant), + size: schema?.size && Object.keys(schema?.size), + }; +}; + +const getKeysFromSlots = (o: { + [slot: string]: { variants: ConfigSchema }; +}) => { + let v = new Set(); + let s = new Set(); + + Object.values(o).forEach(value => { + v = new Set([...v, ...Object.keys(value.variants?.variant ?? {})]); + s = new Set([...s, ...Object.keys(value.variants?.size ?? {})]); + }); + + return { variant: [...v], size: [...s] }; +}; + +/** + * Get variants and sizes (= apperances) from a component + */ +export const getAppearance = ( + name: keyof Theme['components'], + theme: Theme +) => { + const styles = theme.components[name] || {}; + const appearances = + 'variants' in styles + ? getKeys(styles.variants as ConfigSchema) + : getKeysFromSlots(styles); + + return appearances; +}; diff --git a/docs/ui/AppearanceDemo.tsx b/docs/ui/AppearanceDemo.tsx index 8b67e11b12..51e2b570d4 100644 --- a/docs/ui/AppearanceDemo.tsx +++ b/docs/ui/AppearanceDemo.tsx @@ -1 +1,111 @@ -export const AppearanceDemo = () => {}; +import { getAppearance } from '@/lib/utils'; +import { registry } from '@/registry/demos'; +import { + Card, + FieldGroup, + MarigoldProvider, + OverlayContainerProvider, + Select, +} from '@/ui'; +import type { Theme } from '@/ui'; +import type { ComponentType, ReactNode } from 'react'; +import { useState } from 'react'; + +import { useThemeSwitch } from '@/ui/ThemeSwitch'; + +// Props +// --------------- +export interface AppearanceDemoProps { + component: keyof Theme['components']; + disableLabelWidth?: boolean; +} + +// Component +// --------------- +export const AppearanceDemo = ({ + component, + disableLabelWidth, +}: AppearanceDemoProps) => { + const name = `${component.toLowerCase()}-appearance` as keyof typeof registry; + + if (!registry[name]) { + throw Error(`No demo with name "${name}" found in the registry.`); + } + + const Demo: ComponentType = registry[name].demo; + const { current, themes } = useThemeSwitch(); + const theme = themes[current]; + const appearance = getAppearance(component, theme); + + const [selected, setSelected] = useState({ + variant: 'default', + size: 'default', + }); + + const Wrapper = ({ children }: { children: ReactNode }) => + current === 'core' && !disableLabelWidth ? ( + {children} + ) : ( + children + ); + + return ( + <> +

+ The appearance of a component can be customized using the{' '} + variant and size props. These props adjust the + visual style and dimensions of the component, available values are based + on the active theme. +

+ +
+ + +
+
+ + +
+ + + +
+
+
+
+
+ + ); +}; diff --git a/docs/ui/AppearanceTable.tsx b/docs/ui/AppearanceTable.tsx index b32c0965e2..cbe9be2e8d 100644 --- a/docs/ui/AppearanceTable.tsx +++ b/docs/ui/AppearanceTable.tsx @@ -1,6 +1,7 @@ 'use client'; -import { Card, ConfigSchema, Inline, Table, Text, Theme } from '@/ui'; +import { getAppearance } from '@/lib/utils'; +import { Card, Inline, Table, Text, Theme } from '@/ui'; import { useThemeSwitch } from './ThemeSwitch'; import { BlankCanvas } from './icons'; @@ -9,27 +10,6 @@ export interface AppearanceTableProps { component: keyof Theme['components']; } -const getKeys = (schema: ConfigSchema) => { - return { - variant: schema?.variant && Object.keys(schema?.variant), - size: schema?.size && Object.keys(schema?.size), - }; -}; - -const getKeysFromSlots = (o: { - [slot: string]: { variants: ConfigSchema }; -}) => { - let v = new Set(); - let s = new Set(); - - Object.values(o).forEach(value => { - v = new Set([...v, ...Object.keys(value.variants?.variant ?? {})]); - s = new Set([...s, ...Object.keys(value.variants?.size ?? {})]); - }); - - return { variant: [...v], size: [...s] }; -}; - export const AppearanceTable = ({ component }: AppearanceTableProps) => { const { current, themes } = useThemeSwitch(); @@ -37,11 +17,7 @@ export const AppearanceTable = ({ component }: AppearanceTableProps) => { return null; } - const styles = themes[current].components[component] || {}; - const appearances = - 'variants' in styles - ? getKeys(styles.variants as ConfigSchema) - : getKeysFromSlots(styles); + const appearances = getAppearance(component, themes[current]); return ( diff --git a/docs/ui/ComponentDemo.tsx b/docs/ui/ComponentDemo.tsx index 35065e69e8..a865c17f6f 100644 --- a/docs/ui/ComponentDemo.tsx +++ b/docs/ui/ComponentDemo.tsx @@ -8,8 +8,6 @@ import { } from '@/ui'; import type { ReactNode } from 'react'; -import { type Theme } from '@marigold/system'; - import { useThemeSwitch } from '@/ui/ThemeSwitch'; // Props @@ -48,7 +46,7 @@ export const ComponentDemo = ({ const Wrapper = ({ children }: { children: ReactNode }) => current === 'core' && !disableLabelWidth ? ( - {children} + {children} ) : ( children ); @@ -61,13 +59,13 @@ export const ComponentDemo = ({ Code - +
- +
diff --git a/docs/ui/ThemeSwitch.tsx b/docs/ui/ThemeSwitch.tsx index 35a3585b64..e8d93ea1e1 100644 --- a/docs/ui/ThemeSwitch.tsx +++ b/docs/ui/ThemeSwitch.tsx @@ -1,8 +1,9 @@ 'use client'; import { type Theme } from '@/ui'; -import React, { +import { ReactNode, + createContext, useCallback, useContext, useEffect, @@ -15,19 +16,26 @@ import { useRouter, useSearchParams } from 'next/navigation'; // Context // --------------- export interface ThemeSwitchContextType { - current: string | undefined; + current: string; themes: { [name: string]: Theme }; updateTheme: Function; } -export const Context = React.createContext({ - current: undefined, - themes: {}, -} as ThemeSwitchContextType); +export const Context = createContext(null); // Hook // --------------- -export const useThemeSwitch = () => useContext(Context); +export const useThemeSwitch = () => { + const ctx = useContext(Context); + + if (!ctx) { + throw new Error( + 'The "useThemeSwitch" hook can only be used within a component!' + ); + } + + return ctx; +}; // Component // --------------- diff --git a/docs/ui/mdx.tsx b/docs/ui/mdx.tsx index 4c838a1af2..6b72bf41ba 100644 --- a/docs/ui/mdx.tsx +++ b/docs/ui/mdx.tsx @@ -15,12 +15,14 @@ import { List, Scrollable, SectionMessage, + Select, Stack, Table, Tabs, Text, Tiles, } from './'; +import { AppearanceDemo } from './AppearanceDemo'; import { AppearanceTable } from './AppearanceTable'; import { ColorTokenTable } from './ColorTokens'; import { ComponentDemo } from './ComponentDemo'; @@ -136,6 +138,7 @@ const components = { // Docs Components AlignmentsX, AlignmentsY, + AppearanceDemo, AppearanceTable, BorderRadius, Breakpoints, @@ -159,11 +162,12 @@ const components = { Columns, Headline, List, - SectionMessage, Scrollable, + SectionMessage, + Select, Stack, - Tabs, Table, + Tabs, Text, Tiles, }; diff --git a/themes/theme-b2b/src/components/ListBox.styles.ts b/themes/theme-b2b/src/components/ListBox.styles.ts index 252f74eae7..c851efa697 100644 --- a/themes/theme-b2b/src/components/ListBox.styles.ts +++ b/themes/theme-b2b/src/components/ListBox.styles.ts @@ -5,7 +5,7 @@ const font = 'font-body text-text-base'; export const ListBox: ThemeComponent<'ListBox'> = { container: cva([ - 'bg-bg-surface border-border-light mt-[2px] rounded-sm border border-solid', + 'bg-bg-surface border-border-light mt-0.5 rounded-sm border border-solid', 'data-error:border-border-error', ]), list: cva([ diff --git a/themes/theme-docs/src/components/Card.styles.ts b/themes/theme-docs/src/components/Card.styles.ts index e9425c6d60..e8651eed40 100644 --- a/themes/theme-docs/src/components/Card.styles.ts +++ b/themes/theme-docs/src/components/Card.styles.ts @@ -1,12 +1,15 @@ import { ThemeComponent, cva } from '@marigold/system'; export const Card: ThemeComponent<'Card'> = cva( - ['bg-bg-surface rounded-xl border shadow'], + [ + 'bg-bg-surface border-secondary-300 relative overflow-hidden rounded-xl border shadow', + ], { variants: { variant: { default: 'p-6', hovering: 'p-6 transition-shadow hover:cursor-pointer hover:shadow-md', + content: 'my-6', }, }, defaultVariants: { diff --git a/themes/theme-docs/src/components/Field.styles.ts b/themes/theme-docs/src/components/Field.styles.ts new file mode 100644 index 0000000000..7e7e189536 --- /dev/null +++ b/themes/theme-docs/src/components/Field.styles.ts @@ -0,0 +1,21 @@ +import { ThemeComponent, cva } from '@marigold/system'; + +export const Field: ThemeComponent<'Field'> = cva('grid gap-y-0.5', { + variants: { + variant: { + default: '', + floating: [ + 'grid-cols-[min-content_auto] grid-rows-[auto_auto]', + 'items-center', + ], + }, + size: { + default: 'gap-x-3', + small: 'gap-x-2', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, +}); diff --git a/themes/theme-docs/src/components/HelpText.styles.ts b/themes/theme-docs/src/components/HelpText.styles.ts new file mode 100644 index 0000000000..52b5dca4c6 --- /dev/null +++ b/themes/theme-docs/src/components/HelpText.styles.ts @@ -0,0 +1,6 @@ +import { ThemeComponent, cva } from '@marigold/system'; + +export const HelpText: ThemeComponent<'HelpText'> = { + container: cva(), + icon: cva(), +}; diff --git a/themes/theme-docs/src/components/Input.styles.ts b/themes/theme-docs/src/components/Input.styles.ts new file mode 100644 index 0000000000..e406f46568 --- /dev/null +++ b/themes/theme-docs/src/components/Input.styles.ts @@ -0,0 +1,28 @@ +// Box +// --------------- +export const inputBox = 'border border-border rounded text-text-primary'; +export const inputBackground = 'bg-white'; + +// Spacing +// --------------- +export const xSpacing = { + default: 'px-3', + small: 'px-2.5', +}; + +export const ySpacing = { + default: 'py-2', + small: 'py-1.5', +}; + +export const inputSpacing = { + default: `${xSpacing.default} ${ySpacing.default}`, + small: `${xSpacing.small} ${ySpacing.small}`, +}; + +// States +// --------------- +export const inputFocus = ''; +export const inputDisabled = ''; +export const inputError = ''; +export const inputHover = ''; diff --git a/themes/theme-docs/src/components/Label.styles.ts b/themes/theme-docs/src/components/Label.styles.ts new file mode 100644 index 0000000000..e9105071f0 --- /dev/null +++ b/themes/theme-docs/src/components/Label.styles.ts @@ -0,0 +1,38 @@ +import { ThemeComponent, cva } from '@marigold/system'; + +export const Label: ThemeComponent<'Label'> = { + container: cva('', { + variants: { + variant: { + default: '', + floating: [ + 'z-10 col-start-1 row-start-1', + 'pointer-events-none', + 'text-secondary-400 text-nowrap', + 'after:content-[":"]', + ], + }, + size: { + default: 'text-sm', + small: 'text-xs', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + compoundVariants: [ + { + variant: 'floating', + size: 'default', + className: 'pl-4', + }, + { + variant: 'floating', + size: 'small', + className: 'pl-3', + }, + ], + }), + indicator: cva(), +}; diff --git a/themes/theme-docs/src/components/ListBox.styles.ts b/themes/theme-docs/src/components/ListBox.styles.ts new file mode 100644 index 0000000000..407d414d61 --- /dev/null +++ b/themes/theme-docs/src/components/ListBox.styles.ts @@ -0,0 +1,33 @@ +import { ThemeComponent, cva } from '@marigold/system'; + +export const ListBox: ThemeComponent<'ListBox'> = { + container: cva([ + 'bg-bg-surface-overlay border-border rounded border drop-shadow-lg', + ]), + list: cva([ + 'outline-none', + 'p-1', + 'sm:max-h-[45vh] md:max-h-[75vh] lg:max-h-[75vh]', + ]), + option: cva( + [ + 'text-text-primary', + 'cursor-pointer rounded outline-none', + 'rac-hover:bg-bg-hover rac-focus:bg-bg-hover', + 'aria-selected:bg-bg-hover', + ], + { + variants: { + size: { + default: 'p-2', + small: 'px-2 py-1 text-sm', + }, + }, + defaultVariants: { + size: 'small', + }, + } + ), + section: cva(), + sectionTitle: cva(), +}; diff --git a/themes/theme-docs/src/components/Select.styles.ts b/themes/theme-docs/src/components/Select.styles.ts new file mode 100644 index 0000000000..7c2adc3cce --- /dev/null +++ b/themes/theme-docs/src/components/Select.styles.ts @@ -0,0 +1,57 @@ +import { ThemeComponent, cva } from '@marigold/system'; + +import { inputBackground, inputBox, inputSpacing } from './Input.styles'; + +export const Select: ThemeComponent<'Select'> = { + icon: cva('text-secondary-400', { + variants: { + variant: { + default: '', + floating: 'justify-self-end', + }, + size: { + default: '', + small: 'size-3', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }), + select: cva([inputBox, inputBackground, 'outline-none'], { + variants: { + variant: { + default: 'gap-2', + floating: [ + 'shadow', + 'col-span-full row-start-1 grid grid-cols-subgrid grid-rows-subgrid', + // selected value and caret get moved to 2nd col + '*:row-star-1 *:col-start-2 *:text-left', + // So the button gap is not used to separate label from selected value + 'gap-[inherit]', + ], + }, + size: { + default: [inputSpacing.default], + small: [inputSpacing.small, 'text-xs'], + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + compoundVariants: [ + { + variant: 'floating', + size: 'default', + className: 'px-4 py-1.5', + }, + { + variant: 'floating', + size: 'small', + className: 'px-3', + }, + ], + }), +}; diff --git a/themes/theme-docs/src/components/index.ts b/themes/theme-docs/src/components/index.ts index fd5c2d4b81..e36cfca855 100644 --- a/themes/theme-docs/src/components/index.ts +++ b/themes/theme-docs/src/components/index.ts @@ -3,11 +3,17 @@ export * from './Card.styles'; export * from './Dialog.styles'; export * from './Header.styles'; export * from './Headline.styles'; +export * from './Field.styles'; +export * from './HelpText.styles'; +export * from './Input.styles'; +export * from './Label.styles'; export * from './Link.styles'; export * from './List.styles'; +export * from './ListBox.styles'; export * from './Menu.styles'; export * from './Message.styles'; export * from './Popover.styles'; +export * from './Select.styles'; export * from './Tabs.styles'; export * from './Table.styles'; export * from './Text.styles';