Implement Storybook controls for some categories.master
@@ -6,7 +6,9 @@ | |||
"react/button-has-type": "off", | |||
"import/no-extraneous-dependencies": "off", | |||
"no-tabs": "off", | |||
"indent": "off" | |||
"indent": "off", | |||
"react/jsx-indent": "off", | |||
"react/jsx-indent-props": "off" | |||
}, | |||
"extends": [ | |||
"lxsmnsyc/typescript/react" | |||
@@ -18,6 +18,18 @@ const meta: Meta<typeof Component> = { | |||
control: { type: 'select' }, | |||
options: Button.AVAILABLE_SIZES, | |||
}, | |||
subtext: { | |||
control: { type: 'text' }, | |||
}, | |||
badge: { | |||
control: { type: 'text' }, | |||
}, | |||
icon: { | |||
control: { type: 'text' }, | |||
}, | |||
children: { | |||
control: { type: 'text' }, | |||
}, | |||
onClick: { | |||
table: { | |||
disable: true, | |||
@@ -25,15 +37,18 @@ const meta: Meta<typeof Component> = { | |||
action: 'clicked', | |||
}, | |||
}, | |||
args: Component.defaultProps ?? {}, | |||
args: { | |||
...(Component.defaultProps ?? {}), | |||
subtext: 'Subtext', | |||
badge: '1', | |||
children: 'Button', | |||
}, | |||
}; | |||
export const ActionButton = (args: Omit<Props, 'ref'>) => ( | |||
<Component | |||
{...args} | |||
> | |||
Button | |||
</Component> | |||
/> | |||
); | |||
export default meta; |
@@ -44,9 +44,13 @@ export interface ActionButtonProps extends Omit<React.HTMLProps<ActionButtonDeri | |||
*/ | |||
compact?: boolean; | |||
/** | |||
* Should the children's height be variable? | |||
* Graphical representation of the component. | |||
*/ | |||
variableChildrenHeight?: boolean; | |||
icon?: React.ReactNode; | |||
/** | |||
* Should the graphical representation of the component be placed after the children? | |||
*/ | |||
iconAfterChildren?: boolean; | |||
} | |||
/** | |||
@@ -66,7 +70,8 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB | |||
compact = false as const, | |||
className, | |||
block = false as const, | |||
variableChildrenHeight = false as const, | |||
icon, | |||
iconAfterChildren = false as const, | |||
...etcProps | |||
}, | |||
forwardedRef, | |||
@@ -106,37 +111,52 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB | |||
> | |||
<span | |||
className={clsx( | |||
'flex-auto min-w-0', | |||
{ | |||
'text-left': compact || menuItem, | |||
'text-center': !(compact || menuItem), | |||
}, | |||
)} | |||
'flex-auto min-w-0 flex items-center gap-2', | |||
iconAfterChildren ? 'flex-row-reverse' : 'flex-row', | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'block uppercase font-bold w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded', | |||
{ | |||
'h-[1.1em]': !variableChildrenHeight, | |||
}, | |||
)} | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
{subtext && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
{icon && ( | |||
<span | |||
data-testid="icon" | |||
> | |||
{icon} | |||
</span> | |||
)} | |||
{(children || subtext) && ( | |||
<span | |||
className={clsx( | |||
'min-w-0 flex-auto', | |||
{ | |||
'text-left': compact || menuItem, | |||
'text-center': !(compact || menuItem), | |||
}, | |||
)} | |||
> | |||
{children && ( | |||
<span | |||
className={clsx( | |||
'block uppercase font-bold w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded h-[1.1em]', | |||
)} | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
)} | |||
{subtext && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
)} | |||
</span> | |||
{badge && ( | |||
<> | |||
@@ -177,5 +197,6 @@ ActionButton.defaultProps = { | |||
menuItem: false as const, | |||
size: 'medium' as const, | |||
compact: false as const, | |||
variableChildrenHeight: false as const, | |||
icon: undefined, | |||
iconAfterChildren: false as const, | |||
}; |
@@ -10,6 +10,12 @@ const meta: Meta<typeof Component> = { | |||
disable: true, | |||
} | |||
}, | |||
label: { | |||
control: { type: 'text' }, | |||
}, | |||
hint: { | |||
control: { type: 'text' }, | |||
}, | |||
onChange: { | |||
table: { | |||
disable: true, | |||
@@ -17,7 +23,11 @@ const meta: Meta<typeof Component> = { | |||
action: 'changed', | |||
}, | |||
}, | |||
args: Component.defaultProps ?? {}, | |||
args: { | |||
...(Component.defaultProps ?? {}), | |||
label: 'ファイル選択', | |||
hint: 'ファイルを選択してください。', | |||
}, | |||
}; | |||
export const FileSelectBox = (args: Omit<Props, 'ref'>) => ( | |||
@@ -5,6 +5,21 @@ import { ComboBox as Component, ComboBoxProps as Props } from '.'; | |||
const meta: Meta<typeof Component> = { | |||
component: Component, | |||
argTypes: { | |||
label: { | |||
control: { type: 'text' }, | |||
}, | |||
hint: { | |||
control: { type: 'text' }, | |||
}, | |||
indicator: { | |||
control: { type: 'text' }, | |||
}, | |||
children: { | |||
control: { type: 'text' }, | |||
}, | |||
length: { | |||
control: { type: 'number' }, | |||
}, | |||
onChange: { | |||
table: { | |||
disable: true, | |||
@@ -18,10 +33,22 @@ const meta: Meta<typeof Component> = { | |||
}, | |||
}; | |||
export const ComboBox = (args: Omit<Props, 'ref'>) => ( | |||
export const ComboBox = ({ children = '', ...args }: Omit<Props, 'ref'>) => ( | |||
<Component | |||
{...args} | |||
/> | |||
> | |||
{(children ? children.toString() : '').split('\n').filter(s => s.length > 0).map((s, i) => { | |||
const [label, value = label] = s.split(':').map(s => s.trim()) as string[]; | |||
return ( | |||
<option | |||
key={`${s}:${i}`} | |||
value={value} | |||
> | |||
{label} | |||
</option> | |||
); | |||
})} | |||
</Component> | |||
); | |||
export default meta; |
@@ -5,6 +5,15 @@ import { DropdownSelect as Component, DropdownSelectProps as Props } from '.'; | |||
const meta: Meta<typeof Component> = { | |||
component: Component, | |||
argTypes: { | |||
label: { | |||
control: { type: 'text' }, | |||
}, | |||
hint: { | |||
control: { type: 'text' }, | |||
}, | |||
children: { | |||
control: { type: 'text' }, | |||
}, | |||
onChange: { | |||
table: { | |||
disable: true, | |||
@@ -17,10 +26,22 @@ const meta: Meta<typeof Component> = { | |||
}, | |||
}; | |||
export const DropdownSelect = (args: Omit<Props, 'ref'>) => ( | |||
export const DropdownSelect = ({ children = '', ...args }: Omit<Props, 'ref'>) => ( | |||
<Component | |||
{...args} | |||
/> | |||
> | |||
{(children ? children.toString() : '').split('\n').filter(s => s.length > 0).map((s, i) => { | |||
const [label, value = label] = s.split(':').map(s => s.trim()) as string[]; | |||
return ( | |||
<option | |||
key={`${s}:${i}`} | |||
value={value} | |||
> | |||
{label} | |||
</option> | |||
); | |||
})} | |||
</Component> | |||
); | |||
export default meta; |
@@ -1,10 +1,22 @@ | |||
import * as React from 'react'; | |||
import type { Meta } from '@storybook/react'; | |||
import { MenuSelect as Component, MenuSelectProps as Props } from '.'; | |||
import type {Meta} from '@storybook/react'; | |||
import {MenuSelect as Component, MenuSelectProps as Props} from '.'; | |||
const meta: Meta<typeof Component> = { | |||
component: Component, | |||
argTypes: { | |||
label: { | |||
control: { type: 'text' }, | |||
}, | |||
hint: { | |||
control: { type: 'text' }, | |||
}, | |||
indicator: { | |||
control: { type: 'text' }, | |||
}, | |||
children: { | |||
control: { type: 'text' }, | |||
}, | |||
onChange: { | |||
table: { | |||
disable: true, | |||
@@ -17,10 +29,22 @@ const meta: Meta<typeof Component> = { | |||
}, | |||
}; | |||
export const MenuSelect = (args: Omit<Props, 'ref'>) => ( | |||
export const MenuSelect = ({ children = '', ...args }: Omit<Props, 'ref'>) => ( | |||
<Component | |||
{...args} | |||
/> | |||
> | |||
{(children ? children.toString() : '').split('\n').filter(s => s.length > 0).map((s, i) => { | |||
const [label, value = label] = s.split(':').map(s => s.trim()) as string[]; | |||
return ( | |||
<option | |||
key={`${s}:${i}`} | |||
value={value} | |||
> | |||
{label} | |||
</option> | |||
); | |||
})} | |||
</Component> | |||
); | |||
export default meta; |
@@ -2,7 +2,10 @@ | |||
"root": true, | |||
"rules": { | |||
"react/jsx-props-no-spreading": "off", | |||
"no-tabs": "off" | |||
"no-tabs": "off", | |||
"indent": "off", | |||
"react/jsx-indent": "off", | |||
"react/jsx-indent-props": "off" | |||
}, | |||
"extends": [ | |||
"lxsmnsyc/typescript/react" | |||
@@ -18,6 +18,21 @@ const meta: Meta<typeof Component> = { | |||
control: { type: 'select' }, | |||
options: Button.AVAILABLE_SIZES, | |||
}, | |||
subtext: { | |||
control: { type: 'text' }, | |||
}, | |||
badge: { | |||
control: { type: 'text' }, | |||
}, | |||
icon: { | |||
control: { type: 'text' }, | |||
}, | |||
children: { | |||
control: { type: 'text' }, | |||
}, | |||
href: { | |||
control: { type: 'text' }, | |||
}, | |||
onClick: { | |||
table: { | |||
disable: true, | |||
@@ -31,9 +46,7 @@ const meta: Meta<typeof Component> = { | |||
export const LinkButton = (args: Omit<LinkButtonProps, 'ref'>) => ( | |||
<Component | |||
{...args} | |||
> | |||
Button | |||
</Component> | |||
/> | |||
); | |||
export default meta; |
@@ -10,7 +10,7 @@ export type LinkButtonDerivedElement = HTMLAnchorElement; | |||
/** | |||
* Props of the {@link LinkButton} component. | |||
*/ | |||
export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'size'> { | |||
export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'href' | 'size'> { | |||
/** | |||
* Should the component occupy the whole width of its parent? | |||
*/ | |||
@@ -48,9 +48,17 @@ export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButto | |||
*/ | |||
disabled?: boolean; | |||
/** | |||
* Should the children's height be variable? | |||
* Graphical representation of the component. | |||
*/ | |||
variableChildrenHeight?: boolean; | |||
icon?: React.ReactNode; | |||
/** | |||
* Should the graphical representation of the component be placed after the children? | |||
*/ | |||
iconAfterChildren?: boolean; | |||
/** | |||
* URL to navigate to. | |||
*/ | |||
href?: string | unknown; | |||
} | |||
/** | |||
@@ -71,7 +79,8 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP | |||
disabled = false, | |||
href, | |||
style, | |||
variableChildrenHeight = false as const, | |||
icon, | |||
iconAfterChildren = false as const, | |||
...etcProps | |||
}, | |||
forwardedRef, | |||
@@ -115,38 +124,53 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP | |||
style={style} | |||
> | |||
<span | |||
className={clsx( | |||
'flex-auto min-w-0', | |||
{ | |||
'text-left': compact || menuItem, | |||
'text-center': !(compact || menuItem), | |||
}, | |||
)} | |||
className={clsx( | |||
'flex-auto min-w-0 flex items-center gap-2', | |||
iconAfterChildren ? 'flex-row-reverse' : 'flex-row', | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'block uppercase font-bold w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded', | |||
{ | |||
'h-[1.1em]': !variableChildrenHeight, | |||
}, | |||
)} | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
{subtext && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
{icon && ( | |||
<span | |||
data-testid="icon" | |||
> | |||
{icon} | |||
</span> | |||
)} | |||
{(children || subtext) && ( | |||
<span | |||
className={clsx( | |||
'min-w-0 flex-auto', | |||
{ | |||
'text-left': compact || menuItem, | |||
'text-center': !(compact || menuItem), | |||
}, | |||
)} | |||
> | |||
{children && ( | |||
<span | |||
className={clsx( | |||
'block uppercase font-bold w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded h-[1.1em]', | |||
)} | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
)} | |||
{subtext && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
)} | |||
</span> | |||
{badge && ( | |||
<> | |||
@@ -189,5 +213,7 @@ LinkButton.defaultProps = { | |||
subtext: undefined, | |||
block: false, | |||
disabled: false, | |||
variableChildrenHeight: false as const, | |||
icon: undefined, | |||
iconAfterChildren: false as const, | |||
href: undefined, | |||
}; |
@@ -39,6 +39,39 @@ const config: Config = { | |||
'inherit': 'inherit', | |||
'transparent': 'transparent', | |||
}, | |||
extend: { | |||
fontSize: { | |||
'lg': '1.125em', | |||
'xl': '1.25em', | |||
'2xl': '1.5em', | |||
'3xl': '1.75em', | |||
'4xl': '2em', | |||
'5xl': '3em', | |||
'6xl': '4em', | |||
'xxs': '0.625rem', | |||
}, | |||
borderRadius: { | |||
inherit: 'inherit', | |||
}, | |||
minWidth: { | |||
6: '1.5rem', | |||
10: '2.5rem', | |||
12: '3rem', | |||
16: '4rem', | |||
48: '12rem', | |||
64: '16rem', | |||
}, | |||
minHeight: { | |||
6: '1.5rem', | |||
10: '2.5rem', | |||
12: '3rem', | |||
16: '4rem', | |||
64: '16rem', | |||
}, | |||
strokeWidth: { | |||
3: '3', | |||
}, | |||
}, | |||
}, | |||
plugins: [], | |||
}; | |||