UNPKG

@kadconsulting/dry

Version:
207 lines (206 loc) 15.8 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useRef, useMemo, useState, useLayoutEffect, } from 'react'; import { Bank, Mail01, SearchMd, CoinsHand, HelpCircle, InfoCircle, AlertCircle, ChevronDown, CreditCard01, ShoppingCart03, } from '~components/Icons/paths'; import './TextInputStories.scss'; import TextInput from './TextInput'; import { Icon, IconSizes } from '..'; import darkTheme from '~themes/default/palette/dark'; import { inferThemeType } from '~internal/utilities'; import lightTheme from '~themes/default/palette/light'; import { ThemeAwareStory } from '~internal/components'; import { ThemeProvider } from '~components/ThemeProvider'; import { BASE_INPUT_PADDING_HORIZONTAL } from './TextInput'; import { TextSm, TextMd, TextXs } from '~components/Typography'; export default { title: 'Components/FormInputs/TextInput/TextInput Examples', tab: 'TextInput', tags: ['autodocs'], component: TextInput, argTypes: { // Define controls for component props here }, }; /** The default is a bare-bones input with no adornments or icons */ export const Default = { render: (args, context) => { const themeType = inferThemeType(context); return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { ...args }) }) })); }, }; /** * Untitled UI's inputs pair a help icon right adornment with the bottom helper text when the input is valid / not dirty, * and an error icon with error text when the input is invalid. While we could codify this relationship within a * component assembly, at the time of writing, we don't have any examples of this Untitled UI usage, so here is how the * constituent components can be composed to produce that experience. * * Type in the input to make it "invalid"; notice the icon change and the color change of the constituent components: **/ export const UntitledHintOrErrorUX = { render: (args, context) => { const themeType = inferThemeType(context); const [inputValue, setInputValue] = useState(''); const RightAdornment = useMemo(() => { return inputValue ? (_jsx(Icon, { width: 16, height: 16, Path: AlertCircle, PathProps: { stroke: 'error', } })) : (_jsx(Icon, { width: 16, height: 16, Path: HelpCircle, PathProps: { stroke: 'gray-400', } })); }, [inputValue]); const HintText = useMemo(() => (inputValue ? undefined : 'This is a hint text to help user.'), [inputValue]); const ErrorText = useMemo(() => (inputValue ? 'This is an error message.' : undefined), [inputValue]); return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { HintText: HintText, ErrorText: ErrorText, InputAdornmentRight: RightAdornment, placeholder: "Whatever you do, don't type anything in here!", onChange: (e) => setInputValue(e.target.value), ...args }) }) })); }, }; /** A simple string can be passed for the Label prop; if so, the text element will be an Untitled TextSm/Medium (see [the Figma file](https://www.figma.com/file/Xt05q1xny4SbE2HHOEFe2j/KAD-MASTER-Untitled-UI?node-id=3531%3A403182&mode=dev)) */ export const SimpleTextLabel = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'simpleTextLabel'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: 'Email', ...args }) }) })); }, }; /** You can however pass whatever component hierarchy you want if you need to support links, icons, or format parts of text within a label. */ export const ComplexLabels = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'complexLabels'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: _jsxs("div", { style: { width: '100%', display: 'flex', justifyContent: 'space-between', }, children: [_jsx(TextSm, { weight: 'medium', children: "Serial Number" }), _jsx("a", { style: { color: 'var(--primary)' }, href: '#', children: _jsx(TextXs, { weight: 'regular', children: "Where do I find this?" }) })] }), ...args }) }) })); }, }; export const ComplexLabelsWithIcon = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'complexLabels'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: _jsxs("div", { style: { display: 'flex', alignContent: 'center' }, children: [_jsx(TextSm, { weight: 'medium', verticalRhythm: false, style: { display: 'inline', marginRight: '6px' }, children: "Serial Number" }), _jsx(Icon, { width: 16, height: 16, Path: InfoCircle, PathProps: { stroke: 'gray-600', } })] }), ...args }) }) })); }, }; /** There isn't really a use case for this within Untitled, but you'll see it pretty pervasively on the web. The whole label (title and description) is clickable to focus the input. */ export const ComplexLabelsTextHierarchy = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'complexLabelsTextHierarchy'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: _jsxs("div", { style: { width: '100%', }, children: [_jsx(TextMd, { weight: 'medium', verticalRhythm: false, children: "Serial number" }), _jsx(TextXs, { weight: 'regular', verticalRhythm: false, children: "The serial number is etched on the back of your device, near the bottom." })] }), ...args }) }) })); }, }; /** Also not exemplified in Untitled, but another common use case for complex labels is to be able to support red required asterisks. */ export const ComplexLabelsRequiredIndicator = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'complexLabelsRequiredIndicator'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: _jsx(_Fragment, { children: _jsxs(TextSm, { weight: 'medium', children: ["Serial Number ", _jsx("sup", { style: { color: 'var(--error)' }, children: "*" })] }) }), ...args }) }) })); }, }; /** A simple string can be passed for the HintText prop; if so, the text element will be an Untitled TextSm/Medium (see [the Figma file](https://www.figma.com/file/Xt05q1xny4SbE2HHOEFe2j/KAD-MASTER-Untitled-UI?node-id=3531%3A403182&mode=dev)) */ export const SimpleHintText = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'simpleHintText'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: 'Serial Number', HintText: 'The serial number is etched on the back of your device, near the bottom.', ...args }) }) })); }, }; /** Similar to labels, you can pass whatever component hierarchy you want if you need to support links, icons, or format parts of text within the helper text. */ export const ComplexHintText = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'complexHintText'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: 'Serial Number', HintText: _jsxs("div", { style: { display: 'flex', alignItems: 'center' }, children: [_jsx(Icon, { height: 16, width: 16, Path: InfoCircle, style: { marginRight: '6px' }, PathProps: { stroke: 'gray-400', } }), _jsxs(TextSm, { weight: 'regular', verticalRhythm: false, color: 'backgroundContrast', style: { display: 'inline' }, children: ["The serial number is etched on the back of your device, near the bottom.", ' ', _jsx("a", { style: { color: 'var(--primary)' }, href: '#', children: "See diagram." })] })] }), ...args }) }) })); }, }; export const InputAdornmentLeftSimple = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'inputAdornmentLeft'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, Label: 'Email', InputAdornmentLeft: _jsx(Icon, { height: 20, width: 20, Path: Mail01, PathProps: { stroke: 'gray-600', } }), HintText: _jsx("div", { style: { display: 'flex', alignItems: 'center' }, children: _jsxs(TextSm, { weight: 'regular', verticalRhythm: false, color: 'backgroundContrast', style: { display: 'inline' }, children: ["Your email will only be used for password recovery purposes.", ' '] }) }), ...args }) }) })); }, }; /** A more complex example of how multiple adornment children can be composed. A select is used as the first child to select the user's funding source, and the second child is used to indicate the user need not worry about including the unit of currency. */ export const InputAdornmentLeftComplex = { render: (_, context) => { const themeType = inferThemeType(context); const id = 'inputAdornmentLeftInteractive'; const leftAdornmentRef = useRef(null); const [leftAdornmentWidth, setLeftAdornmentWidth] = useState(60); const [selected, setSelected] = useState('card'); const CorrespondingIcon = useMemo(() => { if (selected === null) return CoinsHand; return { card: CreditCard01, bank: Bank, 'store-credit': ShoppingCart03, }[selected]; }, [selected]); const InputAdornmentLeft = useMemo(() => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: BASE_INPUT_PADDING_HORIZONTAL, }, children: [_jsxs("select", { style: { width: `${leftAdornmentWidth}px` }, className: 'adornment-select', onChange: (e) => { setSelected(e.target.options[e.target.selectedIndex].getAttribute('data-source')); }, children: [_jsx("option", { "data-source": 'card', children: "Discover **** **** **** 1234" }), _jsx("option", { "data-source": 'bank', children: "Chase Checking ********9876" }), _jsx("option", { "data-source": 'store-credit', children: "Store Credit ($48.96)" })] }), _jsx(Icon, { width: 20, height: 20, Path: CorrespondingIcon, PathProps: { stroke: 'gray-600', } }), _jsx(Icon, { width: 20, height: 20, Path: ChevronDown, PathProps: { stroke: 'gray-400', } }), _jsx(TextMd, { weight: 'regular', children: "$" })] })), [leftAdornmentWidth, CorrespondingIcon]); useLayoutEffect(() => { if (InputAdornmentLeft && leftAdornmentRef.current) setLeftAdornmentWidth(leftAdornmentRef.current.getBoundingClientRect().width + 3 * BASE_INPUT_PADDING_HORIZONTAL // The padding around the adornment, plus the gap ); }, [InputAdornmentLeft]); return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, LabelProps: { htmlFor: id }, InputAdornmentLeft: InputAdornmentLeft, Label: _jsxs("div", { style: { gap: '16px', display: 'flex', marginLeft: '8px', marginBottom: '16px', alignItems: 'center', }, children: [_jsx(Icon, { Path: CoinsHand, size: IconSizes.SMALL, PathProps: { stroke: 'gray-600', } }), _jsxs("div", { children: [_jsx(TextMd, { weight: 'medium', verticalRhythm: false, children: "Funding Source" }), _jsx(TextXs, { weight: 'regular', verticalRhythm: false, children: "Select a funding source and type the amount you'd like to contribute from it." })] })] }) }) }) })); }, }; export const InputAdornmentRight = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'inputAdornmentLeft'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, placeholder: 'Search for anything...', InputAdornmentRight: _jsx(Icon, { width: 20, height: 20, Path: SearchMd, PathProps: { stroke: 'gray-600', } }), ...args }) }) })); }, }; /** * The `disabled` prop is not propagated to interactive elements in adornments automatically, * in case some UIs need their input to be disabled while maintaining its inner adornment's * interactivity. How exactly an adornment's interactivity should be disabled should * be left up to the consumer defining the adornment. */ export const Disabled = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'disabled'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { id: id, disabled: true, placeholder: "Can't touch this", HintText: 'Search is enabled when you add more than one record.', InputAdornmentRight: _jsx(Icon, { width: 20, height: 20, Path: SearchMd, PathProps: { stroke: 'gray-600', } }), ...args }) }) })); }, }; /** */ export const ColorUpdates = { render: (args, context) => { const themeType = inferThemeType(context); const id = 'colorUpdates'; return (_jsx(ThemeProvider, { overrides: { themeType }, themes: { light: lightTheme, dark: darkTheme }, children: _jsx(ThemeAwareStory, { id: context.id, children: _jsx(TextInput, { Label: 'colorUpdates', id: id, placeholder: "Can't touch this", HintText: 'Search is enabled when you add more than one record.', labelColor: 'red', inputTextColor: 'yellow', inputBackgroundColor: 'green', ...args }) }) })); }, }; //# sourceMappingURL=TextInput.stories.js.map