Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

各 Story を CSF3.0 で書き直す #8

Merged
merged 4 commits into from
Jun 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ module.exports = {
files: ['./**/*.stories.tsx'],
rules: {
'import/no-default-export': ['off'],
'react-hooks/rules-of-hooks': ['off'], // StoryObj.render に渡す FC 内で hooks を使えなくなるため無効化する。
},
},
{
Expand Down
7 changes: 7 additions & 0 deletions apps/catalog/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@ module.exports = {
docs: {
autodocs: true,
},
typescript: {
reactDocgenTypescriptOptions: {
// この設定は monorepo 配下にある各種コンポーネントの JSDoc を認識させるために必要。
// cf. https://github.com/storybookjs/storybook/issues/21399#issuecomment-1473800791
include: ['../../../**/*.tsx'],
},
},
};
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
},
"devDependencies": {
"@learn-monorepo-pnpm/tsconfig": "workspace:1.0.0",
"@storybook/react": "7.0.4",
"@types/react": "18.0.35",
"@types/react-dom": "18.0.11",
"react": "18.2.0",
Expand Down
67 changes: 39 additions & 28 deletions packages/core/src/components/inputs/FormLabel/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,47 @@
import { useId } from 'react';
import { type Meta, type StoryObj } from '@storybook/react';
import { FormLabel } from '.';

export default {
component: FormLabel,
};

const BasicPresentation = () => {
const inputId = useId();

return (
<>
<h2>Nest</h2>
<FormLabel label="メールアドレス">
<input type="email" placeholder="taro.ringo@example.com" />
</FormLabel>
} as Meta<typeof FormLabel>;

<h2>Use Id</h2>
<div
style={{
display: 'grid',
gridTemplateColumns: 'auto 1fr',
gap: 16,
alignItems: 'center',
}}
>
<FormLabel label="メールアドレス" htmlFor={inputId} />
<input type="email" id={inputId} placeholder="taro.ringo@example.com" />
</div>
</>
);
/**
* `children` に input 要素を渡すと縦方向に並びます。
*/
export const Nested: StoryObj<typeof FormLabel> = {
args: {
label: 'メールアドレス',
children: <input type="email" placeholder="taro@example.com" />,
},
};

export const Basic = {
render: BasicPresentation,
/**
* ラベルと input 要素を横一列に並べるには `htmlFor` を使います。
*/
export const Horizontal: StoryObj<typeof FormLabel> = {
args: {
label: 'メールアドレス',
htmlFor: `${Math.random()}`,
},
argTypes: {
label: {
type: {
name: 'string',
required: true,
},
},
},
render: ({ htmlFor = 'bbb', label }) => (
<div
style={{
display: 'grid',
gridTemplateColumns: 'auto 1fr',
gap: 16,
alignItems: 'center',
}}
>
<FormLabel label={label} htmlFor={htmlFor} />
<input type="email" id={htmlFor} placeholder="taro.ringo@example.com" />
</div>
),
};
15 changes: 15 additions & 0 deletions packages/core/src/components/inputs/FormLabel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@ import { useId, type ReactNode } from 'react';
import styles from './index.module.scss';

type Props = {
/**
* ラベルとして表示する文字列
*/
label: string;
} & XOR<
{
/**
* 紐付ける input 要素の `id` を指定します。
*/
htmlFor: string;
},
{
/**
* 紐付ける input 要素を指定します。
*/
children: ReactNode;
}
>;

/**
* 主にフォームなどに用いられるユーザーインターフェイスの項目のキャプションを表します。
*
* @remarks
* `htmlFor` と `children` はどちらか一方のみを指定します。両方同時に指定することはできません。
*/
export const FormLabel = ({ label, htmlFor, children }: Props) => {
const tooltipId = useId();

Expand Down
53 changes: 34 additions & 19 deletions packages/core/src/components/inputs/LabeledSlider/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import { useMemo, useState } from 'react';
import { type Meta, type StoryObj } from '@storybook/react';
import { useState } from 'react';
import { LabeledSlider } from '.';

export default {
component: LabeledSlider,
};
} as Meta<typeof LabeledSlider>;

const BasicPresentation = () => {
const [weight, setWeight] = useState(60);
const [height, setHeight] = useState(170);
export const Usage: StoryObj<typeof LabeledSlider> = {
args: {
label: 'Weight',
unit: 'kg',
min: 40,
max: 150,
},
argTypes: {
onChange: {
action: 'changed',
},
},
render: ({ label, unit, min, max }) => {
const [value, setValue] = useState(60);

const calcBMI = useMemo(() => {
const heightMeters = height * 0.01;
return <LabeledSlider label={label} unit={unit} min={min} max={max} value={value} onChange={setValue} />;
},
};

return Math.round(weight / (heightMeters * heightMeters));
}, [weight, height]);
export const BmiChecker: StoryObj<typeof LabeledSlider> = {
render: () => {
const [weight, setWeight] = useState(60);
const [height, setHeight] = useState(170);

return (
<>
<LabeledSlider label="Weight" unit="kg" min={40} max={150} value={weight} onValueChange={setWeight} />
<LabeledSlider label="Height" unit="cm" min={140} max={220} value={height} onValueChange={setHeight} />
<p>BMI: {calcBMI}</p>
</>
);
};
const heightMeters = height * 0.01;
const bmi = Math.round(weight / (heightMeters * heightMeters));

export const Basic = {
render: BasicPresentation,
return (
<>
<LabeledSlider label="Weight" unit="kg" min={40} max={150} value={weight} onChange={setWeight} />
<LabeledSlider label="Height" unit="cm" min={140} max={220} value={height} onChange={setHeight} />
<p>BMI: {bmi}</p>
</>
);
},
};
29 changes: 26 additions & 3 deletions packages/core/src/components/inputs/LabeledSlider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,40 @@ import { type ChangeEvent } from 'react';
import styles from './index.module.scss';

type Props = {
/**
* スライダーのキャプション
*/
label: string;
/**
* 値の単位
*/
unit: string;
/**
* 最小値
*/
min: number;
/**
* 最大値
*/
max: number;
/**
* コントロールの値
*/
value: number;
onValueChange: (value: number) => void;
/**
* 値が変更されたときにコールバックが発生します。
*
* @param value 変更された値
*/
onChange: (value: number) => void;
};

export const LabeledSlider = ({ label, unit, min, max, value, onValueChange }: Props) => {
/**
* ラベルとスライダーの2つの要素で構成され、ラベルには常にスライダーの現在の動的値が表示されます。
*/
export const LabeledSlider = ({ label, unit, min, max, value, onChange }: Props) => {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
onValueChange(Number(e.target.value));
onChange(Number(e.target.value));
};

return (
Expand Down
51 changes: 24 additions & 27 deletions packages/core/src/hooks/useDebouncedState/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,30 @@ export default {
title: 'hooks/useDebouncedState',
};

const BasicPresentation = () => {
const [delay, setDelay] = useState(1000);

const [value, debouncedValue, setValue] = useDebouncedState('', delay);
export const Basic = {
render: () => {
const [delay, setDelay] = useState(1000);

return (
<>
<h2>Basic</h2>
<div>
<label>
Value:
<input value={value} onChange={(e) => setValue(e.target.value)} />
</label>
</div>
<div>
<label>
Delay:
<input type="number" value={delay} onChange={(e) => setDelay(Number(e.target.value))} />
</label>
</div>
<code>
<pre>{JSON.stringify(debouncedValue, null, 2)}</pre>
</code>
</>
);
};
const [value, debouncedValue, setValue] = useDebouncedState('', delay);

export const Basic = {
render: BasicPresentation,
return (
<>
<div>
<label>
Value:
<input value={value} onChange={(e) => setValue(e.target.value)} />
</label>
</div>
<div>
<label>
Delay:
<input type="number" value={delay} onChange={(e) => setDelay(Number(e.target.value))} />
</label>
</div>
<code>
<pre>{JSON.stringify(debouncedValue, null, 2)}</pre>
</code>
</>
);
},
};
4 changes: 3 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.