(
40 | ({ icon, description, intention = 'shade', disabled, className, children, selected, ...rest }, ref) => (
41 |
60 | {icon && (
61 | svg]:h-full [&>svg]:w-full`,
64 | {
65 | shade: `[&_svg]:fill-shade-medium-default`,
66 | negative: `[&_svg]:fill-negative-medium-default`,
67 | }[intention],
68 | disabled && `[&_svg]:fill-shade-medium-disabled`,
69 | ])}
70 | >
71 | {icon}
72 |
73 | )}
74 |
75 |
76 | {children}
77 |
78 |
82 | {description}
83 |
84 |
85 | {selected && (
86 | svg]:h-full [&>svg]:w-full`,
89 | {
90 | shade: `[&_svg]:fill-shade-dark-default`,
91 | negative: `[&_svg]:fill-negative-dark-default`,
92 | }[intention],
93 | disabled && `[&_svg]:fill-shade-dark-disabled`,
94 | ])}
95 | >
96 |
97 |
98 | )}
99 |
100 | ),
101 | );
102 |
--------------------------------------------------------------------------------
/packages/for-ui/src/menu/MenuList.tsx:
--------------------------------------------------------------------------------
1 | import { ElementType, forwardRef, Ref } from 'react';
2 | import MuiMenuList, { MenuListProps as MuiMenuListProps } from '@mui/material/MenuList';
3 | import { fsx } from '../system/fsx';
4 | import { style } from './style';
5 |
6 | export type MenuListProps = MuiMenuListProps
& {
7 | /**
8 | * レンダリングするコンポーネントを指定 (例: h1, p, strong)
9 | * @default span
10 | */
11 | as?: P;
12 |
13 | ref?: Ref
;
14 | };
15 |
16 | const _MenuList =
({
17 | as,
18 | className,
19 | _ref,
20 | ...rest
21 | }: MenuListProps
& {
22 | _ref: Ref
;
23 | }) => (
24 | } className={fsx(style, className)} {...rest} />
25 | );
26 |
27 | export const MenuList = forwardRef((props, ref) => <_MenuList _ref={ref} {...props} />) as unknown as <
28 | P extends ElementType = 'ul',
29 | >(
30 | props: MenuListProps,
31 | ) => JSX.Element;
32 |
--------------------------------------------------------------------------------
/packages/for-ui/src/menu/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Menu';
2 | export * from './MenuItem';
3 | export * from './MenuList';
4 | export * from './MenuDivider';
5 |
--------------------------------------------------------------------------------
/packages/for-ui/src/menu/style.ts:
--------------------------------------------------------------------------------
1 | import { fsx } from '../system/fsx';
2 |
3 | export const style = fsx(
4 | `z-modal bg-shade-white-default shadow-more divide-shade-light-default border-shade-light-default grid w-full min-w-min grid-cols-1 divide-y rounded border px-0 py-1`,
5 | );
6 |
--------------------------------------------------------------------------------
/packages/for-ui/src/modal/Modal.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useState } from '@storybook/addons';
3 | import { Meta } from '@storybook/react/types-6-0';
4 | import { Modal } from '.';
5 | import { Button } from '../button';
6 | import { ModalContent } from './ModalContent';
7 | import { ModalFooter } from './ModalFooter';
8 | import { ModalHeader } from './ModalHeader';
9 |
10 | export default {
11 | title: 'Feedback / Modal',
12 | component: Modal,
13 | } as Meta;
14 |
15 | export const ModalDefault = (): JSX.Element => {
16 | const [showModal, setShowModal] = useState(true);
17 |
18 | const onSubmit = () => {
19 | console.error('submit');
20 | };
21 |
22 | return (
23 |
24 |
Default Modal
25 |
26 |
29 |
30 |
setShowModal(false)}>
31 | 本当に削除してよろしいですか?
32 |
33 |
34 | アカウントを無効にしてもよろしいですか?すべてのデータは完全に削除されます。このアクションは元に戻せません。
35 |
36 |
37 |
38 |
41 |
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/packages/for-ui/src/modal/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 | import MuiBackdrop, { BackdropProps as MuiBackdropProps } from '@mui/material/Backdrop';
3 | import MuiModal, { ModalProps as MuiModalProps } from '@mui/material/Modal';
4 |
5 | type BackdropProps = MuiBackdropProps;
6 |
7 | export type ModalProps = Omit & {
8 | /** Whether the Dialog is open */
9 | open: boolean;
10 |
11 | children: React.ReactNode | React.ReactNode[];
12 |
13 | /** Handler that is called when the 'cancel' button of a dismissable Dialog is clicked. */
14 | onClose?(event: React.MouseEvent | React.KeyboardEvent): void;
15 |
16 | className?: string;
17 | };
18 |
19 | const Backdrop: React.FC = ({ open, children, onClick }) => {
20 | return (
21 |
28 | {children}
29 |
30 | );
31 | };
32 |
33 | export const Modal: React.FC = forwardRef(({ open, onClose, children, className, ...props }, ref) => {
34 | return (
35 |
48 |
49 |
50 | {children}
51 |
52 |
53 |
54 | );
55 | });
56 |
--------------------------------------------------------------------------------
/packages/for-ui/src/modal/ModalContent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export type ModalContentProps = {
4 | children: React.ReactNode;
5 | };
6 |
7 | export const ModalContent: React.FC = ({ children }) => {
8 | return {children}
;
9 | };
10 |
--------------------------------------------------------------------------------
/packages/for-ui/src/modal/ModalFooter.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 |
3 | type Props = {
4 | className?: string;
5 | /** The contents of the Dialog. */
6 | children: ReactNode;
7 | };
8 |
9 | export const ModalFooter: React.FC = ({ children }) => {
10 | return {children}
;
11 | };
12 |
--------------------------------------------------------------------------------
/packages/for-ui/src/modal/ModalHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { Text } from '../text';
3 |
4 | type Props = {
5 | /** The contents of the Dialog. */
6 | children: ReactNode | string;
7 | };
8 |
9 | export const ModalHeader: React.FC = ({ children }) => {
10 | return (
11 |
12 |
13 | {children}
14 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/packages/for-ui/src/modal/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Modal';
2 | export * from './ModalContent';
3 | export * from './ModalHeader';
4 | export * from './ModalFooter';
5 |
--------------------------------------------------------------------------------
/packages/for-ui/src/popper/Popper.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Meta } from '@storybook/react/types-6-0';
3 | import { Button } from '../button/Button';
4 | import { MenuItem } from '../menu';
5 | import { Text } from '../text';
6 | import { Popper } from './Popper';
7 |
8 | export default {
9 | title: 'Navigation / Popper',
10 | component: Popper,
11 | argTypes: {
12 | backgroundColor: { control: 'color' },
13 | },
14 | decorators: [
15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
16 | (Story: any) => (
17 |
18 |
19 |
20 | ),
21 | ],
22 | } as Meta;
23 |
24 | export const Base = () => {
25 | return (
26 |
27 |
28 |
29 | Popper
30 |
31 |
32 |
33 |
プロジェクト}>
34 |
35 |
こんにちは
36 |
こんにちは
37 |
こんにちは
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export const WithCustomClose = () => {
45 | return (
46 |
47 |
48 |
49 | Popper
50 |
51 |
52 |
53 |
TriggerComponent={}>
54 | {({ onClick }) => (
55 |
56 |
57 |
58 |
59 |
60 | )}
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/packages/for-ui/src/popper/Popper.tsx:
--------------------------------------------------------------------------------
1 | import { cloneElement, Fragment, isValidElement, MouseEvent, ReactElement, ReactNode } from 'react';
2 | import { bindPopper, bindTrigger } from 'material-ui-popup-state';
3 | import { usePopupState } from 'material-ui-popup-state/hooks';
4 | import Fade from '@mui/material/Fade';
5 | import Paper from '@mui/material/Paper';
6 | import MuiPopper from '@mui/material/Popper';
7 | import { fsx } from '../system/fsx';
8 |
9 | type PoppeChildrenProps = {
10 | onClick: (e: MouseEvent) => void;
11 | };
12 |
13 | export type PopperProps = {
14 | TriggerComponent: ReactNode;
15 |
16 | children: ReactElement | ((props: PoppeChildrenProps) => ReactElement);
17 | };
18 |
19 | export const Popper = (props: PopperProps) => {
20 | const popupState = usePopupState({
21 | variant: 'popover',
22 | popupId: undefined,
23 | });
24 |
25 | const trigger = bindTrigger(popupState);
26 | let _TriggerComponent = <>>;
27 | if (isValidElement(props.TriggerComponent)) {
28 | _TriggerComponent = cloneElement(props.TriggerComponent, {
29 | ...trigger,
30 | });
31 | }
32 |
33 | return (
34 |
35 | {_TriggerComponent}
36 |
37 |
38 | {({ TransitionProps }) => (
39 |
40 |
45 | {typeof props.children === 'function' ? props.children({ onClick: popupState.close }) : props.children}
46 |
47 |
48 | )}
49 |
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/packages/for-ui/src/popper/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Popper';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/radio/Radio.tsx:
--------------------------------------------------------------------------------
1 | import { FC, forwardRef, ReactNode } from 'react';
2 | import MuiRadio, { RadioProps as MuiRadioProps } from '@mui/material/Radio';
3 | import { fsx } from '../system/fsx';
4 | import { TextDefaultStyler } from '../system/TextDefaultStyler';
5 | import { Text } from '../text';
6 |
7 | export type RadioProps = Omit & {
8 | label?: ReactNode;
9 | nopadding?: boolean;
10 | className?: string;
11 | };
12 |
13 | const Indicator: FC<{ checked: boolean; disabled: boolean }> = ({ checked, disabled }) => (
14 |
21 | );
22 |
23 | export const Radio = forwardRef(
24 | ({ nopadding, label, value, disabled, className, ...rest }, ref) => (
25 |
26 | }
32 | checkedIcon={}
33 | classes={{
34 | root: fsx(nopadding ? `p-0` : `p-1`),
35 | }}
36 | {...rest}
37 | />
38 | (
41 |
46 | )}
47 | />
48 |
49 | ),
50 | );
51 |
--------------------------------------------------------------------------------
/packages/for-ui/src/radio/RadioGroup.tsx:
--------------------------------------------------------------------------------
1 | import { Children, cloneElement, ComponentProps, forwardRef, isValidElement, PropsWithoutRef, ReactNode } from 'react';
2 | import FormControl from '@mui/material/FormControl';
3 | import MuiRadioGroup, { RadioGroupProps as MuiRadioGroupProps } from '@mui/material/RadioGroup';
4 | import { fsx } from '../system/fsx';
5 | import { TextDefaultStyler } from '../system/TextDefaultStyler';
6 | import { Text } from '../text';
7 | import { Radio } from './Radio';
8 |
9 | export type RadioGroupProps = Omit, 'color'> & {
10 | name?: string;
11 | row?: boolean;
12 | disabled?: boolean;
13 | required?: boolean;
14 | label?: ReactNode;
15 | error?: boolean;
16 | helperText?: ReactNode;
17 | className?: string;
18 | children: ReactNode;
19 | };
20 |
21 | export const RadioGroup = forwardRef(
22 | (
23 | { className, name, label, row, defaultValue, error, helperText, children, required = false, disabled, ...rest },
24 | ref,
25 | ) => (
26 |
33 | (
36 |
37 | {/* due to the CSS bug, legend element cannot be styled if contents not specified. But contents makes difficult to style, so wrap by Text. */}
38 |
39 | {children}
40 | {required && *}
41 |
42 |
43 | )}
44 | />
45 |
53 | {
54 | // Pass ref to children (Radio) if provided to RadioGroup
55 | Children.map(children, (child) =>
56 | isValidElement>(child)
57 | ? cloneElement(child, { ref: ref ? ref : undefined })
58 | : child,
59 | )
60 | }
61 |
62 | (
65 |
71 | )}
72 | />
73 |
74 | ),
75 | );
76 |
--------------------------------------------------------------------------------
/packages/for-ui/src/radio/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Radio';
2 | export * from './RadioGroup';
3 |
--------------------------------------------------------------------------------
/packages/for-ui/src/select/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Select';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/skeleton/Skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Children, cloneElement, FC, Fragment, isValidElement, ReactNode } from 'react';
2 | import MuiSkeleton, { SkeletonProps as MuiSkeletonProps } from '@mui/material/Skeleton';
3 | import { fsx } from '../system/fsx';
4 | import { TextProps } from '../text';
5 |
6 | export type SkeletonProps = MuiSkeletonProps & {
7 | loading?: boolean;
8 | className?: string;
9 | count?: number;
10 |
11 | /**
12 | * テキストの代わりに表示する場合はテキストのサイズを指定
13 | */
14 | size?: Exclude['size'], 'inherit'>;
15 | };
16 |
17 | export const Skeleton: FC = ({ loading = false, count = 1, className, children, size, ...rest }) => {
18 | if (!children) {
19 | return (
20 |
37 | );
38 | }
39 | if (loading) {
40 | return (
41 | <>
42 | {[...Array(count)].map((_, idx) => (
43 |
44 | {Children.count(children) > 0 && Children.toArray(children)[0]}
45 |
46 | ))}
47 | >
48 | );
49 | }
50 | if (isValidElement(children)) {
51 | return children;
52 | }
53 | return {children};
54 | };
55 |
56 | const recursiveChildren = (children: ReactNode, empty: ReactNode): ReactNode => {
57 | if (!children) return <>>;
58 |
59 | return Children.map(children, (child: ReactNode) => {
60 | if (!isValidElement(child)) {
61 | return child;
62 | }
63 |
64 | if (!isValidElement(empty)) {
65 | return empty;
66 | }
67 |
68 | if (Children.count(child.props.children) > 1) {
69 | return recursiveChildren(child.props.children, empty);
70 | }
71 | if (Children.count(child.props.children) === 0) {
72 | return {cloneElement(empty, empty.props)};
73 | }
74 | return {cloneElement(child, child.props)};
75 | });
76 | };
77 |
78 | type SkeletonXProps = MuiSkeletonProps & {
79 | loading?: boolean;
80 | empty?: ReactNode;
81 | };
82 |
83 | export const SkeletonX: FC = ({
84 | loading = false,
85 | empty = xxxxxxxxxxxxxxx
,
86 | children,
87 | }) => {
88 | if (loading) {
89 | return {recursiveChildren(children, empty)};
90 | }
91 | if (isValidElement(children)) {
92 | return children;
93 | }
94 | return {children};
95 | };
96 |
--------------------------------------------------------------------------------
/packages/for-ui/src/skeleton/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Skeleton';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/snackbar/Snackbar.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Fragment, useState } from 'react';
2 | import { Meta } from '@storybook/react/types-6-0';
3 | import { Button } from '../button';
4 | import { Text } from '../text';
5 | import { Snackbar, SnackbarProps } from './Snackbar';
6 | import { SnackbarProvider, useSnackbar } from './SnackbarContext';
7 |
8 | export default {
9 | title: 'Feedback / Snackbar',
10 | component: Snackbar,
11 | } as Meta;
12 |
13 | export const Playground = {
14 | args: {
15 | open: true,
16 | message: 'テスト',
17 | autoHide: true,
18 | autoHideDuration: null,
19 | },
20 | };
21 |
22 | export const Stateless = () => 開く} message="操作が完了しました" />;
23 |
24 | export const StatelessAutoHide = () => (
25 | 開く} autoHide message="操作が完了しました" />
26 | );
27 |
28 | export const Stateful = () => {
29 | const [open, setOpen] = useState(false);
30 | const openHandler = () => {
31 | setOpen(true);
32 | };
33 | const closeHandler = () => {
34 | setOpen(false);
35 | };
36 | return (
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export const StatefulAutoHide = () => {
45 | const [open, setOpen] = useState(false);
46 | const openHandler = () => {
47 | setOpen(true);
48 | };
49 | const closeHandler = () => {
50 | setOpen(false);
51 | };
52 | return (
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export const WithContext = () => {
61 | const App = () => {
62 | const { openSnackbar } = useSnackbar();
63 | return (
64 |
65 |
68 |
71 |
72 | );
73 | };
74 | return (
75 |
76 |
77 | Snackbarは1画面に1つのみ表示することを強く推奨しています。この挙動はSnackbarProviderを使うことで簡単に実現できます。
78 |
79 |
80 |
81 | );
82 | };
83 |
--------------------------------------------------------------------------------
/packages/for-ui/src/snackbar/Snackbar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | cloneElement,
3 | forwardRef,
4 | Fragment,
5 | isValidElement,
6 | ReactElement,
7 | SyntheticEvent,
8 | useId,
9 | useState,
10 | } from 'react';
11 | import MuiSnackbar, { SnackbarProps as MuiSnackbarProps, SnackbarCloseReason } from '@mui/material/Snackbar';
12 | import MuiSnackbarContent, { SnackbarContentProps as MuiSnackbarContentProps } from '@mui/material/SnackbarContent';
13 | import { Button } from '../button';
14 | import { fsx } from '../system/fsx';
15 | import { Text } from '../text';
16 |
17 | export type SnackbarProps = MuiSnackbarProps & {
18 | /**
19 | * 自動的にSnackbarが消えるまでの時間を指定
20 | *
21 | * 指定されているときは閉じるボタンは表示されません。
22 | * 消えるまでの時間はミリ秒で指定します。_message_を読むのに十分な時間を与えるようにしてください。
23 | *
24 | * @default 5000
25 | */
26 | autoHideDuration?: MuiSnackbarProps['autoHideDuration'];
27 |
28 | /**
29 | * 自動的にSnackbarを消すかどうかを指定
30 | *
31 | * ユーザーが閉じるボタンを押す あるいは SnackbarProvider下では新しいSnackbarが開かれる ことでのみ閉じられます。
32 | */
33 | autoHide?: boolean;
34 |
35 | /**
36 | * 表示するメッセージを指定
37 | */
38 | message: MuiSnackbarProps['message'];
39 |
40 | /**
41 | * 操作の起点となるコンポーネントを指定
42 | *
43 | * ボタンを押すと即座に表示のように使うことができますが、通常はユーザーの操作と処理の完了は完全に同義ではないため、そのような場面では使用は非推奨です。
44 | */
45 | TriggerComponent?: ReactElement<{ onClick: () => void }>;
46 |
47 | className?: string;
48 | };
49 |
50 | export const Snackbar = forwardRef(
51 | (
52 | {
53 | TriggerComponent,
54 | autoHideDuration = 5000,
55 | autoHide = false,
56 | onClose,
57 | onClick,
58 | message,
59 | action,
60 | className,
61 | TransitionProps,
62 | ...rest
63 | },
64 | ref,
65 | ) => {
66 | const [open, setOpen] = useState(true);
67 | const handleClose = (e: Event | SyntheticEvent, reason: SnackbarCloseReason) => {
68 | onClose?.(e, reason);
69 | setOpen(false);
70 | };
71 | const handleOpen = () => {
72 | setOpen(true);
73 | };
74 | const Trigger =
75 | isValidElement(TriggerComponent) &&
76 | cloneElement(TriggerComponent, {
77 | onClick: handleOpen,
78 | });
79 | return (
80 |
81 | {Trigger}
82 | {
108 | onClick?.(e);
109 | autoHide && handleClose(e, 'escapeKeyDown');
110 | }}
111 | {...rest}
112 | >
113 |
114 |
115 |
116 | );
117 | },
118 | );
119 |
120 | export type SnackbarContentProps = MuiSnackbarContentProps & {
121 | autoHide?: boolean;
122 | onClose?: SnackbarProps['onClose'];
123 | message?: MuiSnackbarContentProps['message'];
124 | action?: MuiSnackbarContentProps['action'];
125 | };
126 |
127 | export const SnackbarContent = forwardRef(
128 | ({ autoHide, message, action, onClose, onClick, className, ...rest }, ref) => {
129 | const messageId = useId();
130 | return (
131 | {
142 | onClick?.(e);
143 | autoHide && onClose?.(e, 'escapeKeyDown');
144 | }}
145 | message={{message}}
146 | action={
147 | action ||
148 | (!autoHide && (
149 |
159 | ))
160 | }
161 | {...rest}
162 | />
163 | );
164 | },
165 | );
166 |
--------------------------------------------------------------------------------
/packages/for-ui/src/snackbar/SnackbarContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, FC, ReactNode, useContext, useState } from 'react';
2 | import { Snackbar, SnackbarProps } from './Snackbar';
3 |
4 | type SnackbarContextValue = {
5 | inProvider: boolean;
6 | snackbar?: SnackbarProps;
7 | enqueue: (props: SnackbarProps) => void;
8 | };
9 |
10 | // TODO: Rewrite using `satisfies` once jest supports TypeScript v4.9
11 | const initialSnackbarContextValue: SnackbarContextValue = {
12 | inProvider: false,
13 | enqueue: () => undefined,
14 | };
15 |
16 | const SnackbarContext = createContext(initialSnackbarContextValue);
17 |
18 | // TODO: Refactor using Portal. See https://react.dev/reference/react-dom/createPortal
19 | const SnackbarView = () => {
20 | const { snackbar } = useContext(SnackbarContext);
21 | if (!snackbar) {
22 | return null;
23 | }
24 | return ;
25 | };
26 |
27 | export type SnackbarProviderProps = {
28 | children: ReactNode;
29 | };
30 |
31 | export const SnackbarProvider: FC = ({ children, ...rest }) => {
32 | const [snackbar, setSnackbar] = useState();
33 | const enqueue = (props: SnackbarProps) => {
34 | setSnackbar(props);
35 | };
36 | return (
37 |
45 | {children}
46 |
47 |
48 | );
49 | };
50 |
51 | type UseSnackbar = {
52 | inProvider: boolean;
53 | openSnackbar: (props: SnackbarProps) => void;
54 | };
55 |
56 | export const useSnackbar = (): UseSnackbar => {
57 | const { inProvider, enqueue } = useContext(SnackbarContext);
58 | return {
59 | inProvider,
60 | openSnackbar: enqueue,
61 | };
62 | };
63 |
--------------------------------------------------------------------------------
/packages/for-ui/src/snackbar/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Snackbar';
2 | export * from './SnackbarContext';
3 |
--------------------------------------------------------------------------------
/packages/for-ui/src/stepper/Step.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import { MdOutlineDone } from 'react-icons/md';
3 | import MuiStep, { StepProps as MuiStepProps } from '@mui/material/Step';
4 | import { StepIconProps as MuiStepIconProps } from '@mui/material/StepIcon';
5 | import MuiStepLabel, { StepLabelProps as MuiStepLabelProps } from '@mui/material/StepLabel';
6 | import { fsx } from '../system/fsx';
7 | import { Text } from '../text';
8 |
9 | export type StepProps = MuiStepProps;
10 |
11 | export type StepLabelProps = MuiStepLabelProps;
12 |
13 | export const Step = forwardRef(({ children, active, className, ...rest }, ref) => (
14 |
15 |
24 |
25 | {children}
26 |
27 |
28 |
29 | ));
30 |
31 | const Icon = ({ completed, active, icon }: Partial) => (
32 |
40 |
44 | {completed ? : icon}
45 |
46 |
47 | );
48 |
--------------------------------------------------------------------------------
/packages/for-ui/src/stepper/Stepper.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Meta, Story } from '@storybook/react/types-6-0';
3 | import { Text } from '../text';
4 | import { Step } from './Step';
5 | import { Stepper } from './Stepper';
6 |
7 | export default {
8 | title: 'Navigation / Stepper',
9 | component: Stepper,
10 | argTypes: {
11 | activeStep: { control: { min: 1 } },
12 | alternativeLabel: { control: 'boolean' },
13 | backgroundColor: { control: 'color' },
14 | },
15 | decorators: [(Story: Story) => ],
16 | } as Meta;
17 |
18 | const steps = ['First', 'Second', 'Third', 'Last'];
19 |
20 | export const LabelBottom: Story = (args) => (
21 |
22 |
23 | Stepper
24 |
25 |
26 | {steps.map((step, index) => (
27 | {step}
28 | ))}
29 |
30 |
31 | {steps.map((step, index) => (
32 | {step}
33 | ))}
34 |
35 |
36 | {steps.map((step, index) => (
37 | {step}
38 | ))}
39 |
40 |
41 | {steps.map((step, index) => (
42 | {step}
43 | ))}
44 |
45 |
46 | );
47 |
48 | export const LabelTrailing: Story = (args) => (
49 |
50 |
51 | Stepper
52 |
53 |
54 | {steps.map((step, index) => (
55 | {step}
56 | ))}
57 |
58 |
59 | {steps.map((step, index) => (
60 | {step}
61 | ))}
62 |
63 |
64 | {steps.map((step, index) => (
65 | {step}
66 | ))}
67 |
68 |
69 | {steps.map((step, index) => (
70 | {step}
71 | ))}
72 |
73 |
74 | );
75 |
--------------------------------------------------------------------------------
/packages/for-ui/src/stepper/Stepper.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import { StepConnector } from '@mui/material';
3 | import MuiStepper, { StepperProps as MuiStepperProps } from '@mui/material/Stepper';
4 | import { fsx } from '../system/fsx';
5 |
6 | export type StepperProps = Omit & {
7 | /**
8 | * ラベルを表示する位置を指定
9 | *
10 | * @default 'bottom'
11 | */
12 | labelPosition?: 'bottom' | 'trailing';
13 |
14 | /**
15 | * @deprecated labelPositionを使用してください
16 | */
17 | alternativeLabel?: MuiStepperProps['alternativeLabel'];
18 |
19 | className?: string;
20 | };
21 |
22 | export const Stepper = forwardRef(
23 | ({ activeStep, alternativeLabel, labelPosition = 'bottom', children, className, ...rest }, ref) => (
24 | .MuiStepConnector-line]:border-primary-dark-default -left-2/4 right-0 top-0 flex h-6 w-full items-center px-0 [&.MuiStepConnector-alternativeLabel]:pl-3 [&.MuiStepConnector-alternativeLabel]:pr-4`,
33 | ),
34 | line: fsx(`border-shade-dark-disabled w-full border-t-2`),
35 | }}
36 | />
37 | }
38 | className={fsx(`gap-1`, className)}
39 | {...rest}
40 | >
41 | {children}
42 |
43 | ),
44 | );
45 |
--------------------------------------------------------------------------------
/packages/for-ui/src/stepper/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Stepper';
2 | export * from './Step';
3 |
--------------------------------------------------------------------------------
/packages/for-ui/src/switch/Switch.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react';
2 | import { Controller, useForm } from 'react-hook-form';
3 | import * as yup from 'yup';
4 | import { yupResolver } from '@hookform/resolvers/yup';
5 | import { Meta } from '@storybook/react/types-6-0';
6 | import { Button } from '../button';
7 | import { fsx } from '../system/fsx';
8 | import { Text } from '../text';
9 | import { Switch } from './Switch';
10 | import { SwitchGroup } from './SwitchGroup';
11 |
12 | export default {
13 | title: 'Form / Switch',
14 | component: Switch,
15 | argTypes: {},
16 | } as Meta;
17 |
18 | export const Basic = (): JSX.Element => {
19 | const { control, handleSubmit } = useForm<{
20 | default1: boolean;
21 | default2: boolean;
22 | disable1: boolean;
23 | disable2: boolean;
24 | }>({
25 | reValidateMode: 'onChange',
26 | });
27 |
28 | const onSubmit = (data: unknown) => console.info(data);
29 |
30 | return (
31 |
73 | );
74 | };
75 |
76 | export const WithReactHookForm = () => {
77 | const schema = yup.object({
78 | autosave: yup.string().required(),
79 | });
80 | type FieldValue = yup.InferType;
81 |
82 | const { register, handleSubmit } = useForm({
83 | resolver: yupResolver(schema),
84 | });
85 | const onSubmit = (data: unknown) => {
86 | console.info(data);
87 | };
88 |
89 | return (
90 |
94 | );
95 | };
96 |
97 | export const WithSwitchGroup = () => {
98 | const allPreferences = {
99 | spring: '春',
100 | summer: '夏',
101 | autumn: '秋',
102 | winter: '冬',
103 | } as const;
104 |
105 | const schema = yup.object({
106 | preferences: yup.object(Object.fromEntries(Object.keys(allPreferences).map((k) => [k, yup.boolean()]))),
107 | });
108 |
109 | type FieldValue = yup.InferType;
110 |
111 | const {
112 | register,
113 | handleSubmit,
114 | formState: { errors },
115 | } = useForm({
116 | resolver: yupResolver(schema),
117 | });
118 | const onSubmit = useCallback((data: unknown) => {
119 | console.info(data);
120 | }, []);
121 | return (
122 |
137 | );
138 | };
139 |
140 | export const CustomLabel = () => (
141 |
144 | ラベル
145 |
146 | }
147 | />
148 | );
149 |
--------------------------------------------------------------------------------
/packages/for-ui/src/switch/Switch.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithRef, forwardRef, ReactNode } from 'react';
2 | import { fsx } from '../system/fsx';
3 | import { TextDefaultStyler } from '../system/TextDefaultStyler';
4 | import { Text } from '../text';
5 |
6 | export type SwitchProps = ComponentPropsWithRef<'input'> & {
7 | label?: ReactNode;
8 | className?: string;
9 | };
10 |
11 | export const Switch = forwardRef(({ label, disabled, className, ...rest }, ref) => (
12 |
13 |
26 | (
29 |
30 | )}
31 | />
32 |
33 | ));
34 |
--------------------------------------------------------------------------------
/packages/for-ui/src/switch/SwitchGroup.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef, ReactNode } from 'react';
2 | import FormControl from '@mui/material/FormControl';
3 | import FormGroup, { FormGroupProps } from '@mui/material/FormGroup';
4 | import { fsx } from '../system/fsx';
5 | import { TextDefaultStyler } from '../system/TextDefaultStyler';
6 | import { Text } from '../text';
7 |
8 | export type SwitchGroupProps = Omit & {
9 | name?: string;
10 | row?: boolean;
11 | required?: boolean;
12 | disabled?: boolean;
13 | label?: ReactNode;
14 | error?: boolean;
15 | helperText?: ReactNode;
16 | className?: string;
17 | children: ReactNode;
18 | };
19 |
20 | export const SwitchGroup = forwardRef(
21 | ({ required, disabled, label, error, row, helperText, className, ...rest }, ref) => {
22 | return (
23 |
30 | (
33 |
34 | {/* due to the CSS bug, legend element cannot be styled if contents not specified. But contents makes difficult to style, so wrap by Text. */}
35 |
36 | {children}
37 | {required && *}
38 |
39 |
40 | )}
41 | />
42 |
43 | (
46 |
52 | )}
53 | />
54 |
55 | );
56 | },
57 | );
58 |
--------------------------------------------------------------------------------
/packages/for-ui/src/switch/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Switch';
2 | export * from './SwitchGroup';
3 |
--------------------------------------------------------------------------------
/packages/for-ui/src/system/PropsCascader.tsx:
--------------------------------------------------------------------------------
1 | import { Children, cloneElement, FC, ForwardedRef, forwardRef, HTMLAttributes, isValidElement, ReactNode } from 'react';
2 |
3 | type PropsCascaderProps> = T & {
4 | children: ReactNode;
5 | };
6 |
7 | type PropsCascaderComponent = >(props: PropsCascaderProps) => ReturnType;
8 |
9 | export const PropsCascader: PropsCascaderComponent = forwardRef(
10 | >(
11 | { children, ...props }: PropsCascaderProps,
12 | ref: ForwardedRef,
13 | ) => {
14 | return Children.map(children, (child: ReactNode) => {
15 | if (!isValidElement(child)) {
16 | return child;
17 | }
18 | return cloneElement(child, { ...props, ...child.props, ref });
19 | });
20 | },
21 | );
22 |
--------------------------------------------------------------------------------
/packages/for-ui/src/system/TextDefaultStyler.tsx:
--------------------------------------------------------------------------------
1 | import { FC, isValidElement, ReactElement, ReactNode } from 'react';
2 |
3 | export type TextDefaultStylerProps = {
4 | /**
5 | * 適用されるラベル等を指定
6 | *
7 | * contentがstringやnumberなどReactElementではない場合にdefaultRendererを用いて描画されます。
8 | * contentがReactElementの場合はdefaultRendererを用いずにそのまま描画されます。
9 | */
10 | content: ReactNode;
11 |
12 | /**
13 | * contentがReactElementではない場合に描画に用いるコンポーネントを指定
14 | */
15 | defaultRenderer: FC<{ children: Exclude }>;
16 | };
17 |
18 | export const TextDefaultStyler: FC = ({ content, defaultRenderer: DefaultRenderer }) => {
19 | if (!content) {
20 | return null;
21 | }
22 | // TODO: To eliminate the gap, `any` is used. (ReactNode's ReactElement is ReactElement, but isValidElement's returned ReactElement is ReactElement)
23 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
24 | if (isValidElement(content)) {
25 | return content;
26 | }
27 | return {content};
28 | };
29 |
--------------------------------------------------------------------------------
/packages/for-ui/src/system/componentType.ts:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithoutRef, ComponentPropsWithRef, ElementType, FC } from 'react';
2 | import { PreservedOmit } from './preservedOmit';
3 |
4 | export type Ref = ComponentPropsWithRef['ref'];
5 |
6 | export type RefProps = { ref?: Ref };
7 |
8 | export type AsProps = {
9 | /**
10 | * レンダリングするコンポーネントを指定 (例: button, a, input)
11 | */
12 | as?: As;
13 | };
14 |
15 | export type ComponentPropsWithAs = AsProps &
16 | Props &
17 | RefProps &
18 | PreservedOmit, keyof (Props & AsProps & RefProps)>;
19 |
20 | export type ElementTypeToHTMLElement = Element extends keyof HTMLElementTagNameMap
21 | ? HTMLElementTagNameMap[Element]
22 | : Element;
23 |
24 | export type Element = ReturnType;
25 |
--------------------------------------------------------------------------------
/packages/for-ui/src/system/fsx.ts:
--------------------------------------------------------------------------------
1 | // fsx merges tailwind classes and other classes along with For Design System Token.
2 | import clsx, { ClassValue } from 'clsx';
3 | import { extendTailwindMerge } from 'tailwind-merge';
4 |
5 | const twMerge = extendTailwindMerge({
6 | classGroups: {
7 | 'font-size': [
8 | {
9 | text: ['xs', 's', 'r', 'xr', 'l', 'xl'],
10 | },
11 | ],
12 | },
13 | });
14 |
15 | export const fsx = (...classes: ClassValue[]): string => twMerge(clsx(classes));
16 |
--------------------------------------------------------------------------------
/packages/for-ui/src/system/preservedOmit.ts:
--------------------------------------------------------------------------------
1 | export type PreservedOmit = {
2 | [Property in keyof T as Exclude]: T[Property];
3 | };
4 |
--------------------------------------------------------------------------------
/packages/for-ui/src/system/walkChildren.ts:
--------------------------------------------------------------------------------
1 | import { Children, isValidElement, ReactNode } from 'react';
2 |
3 | // walkChildren visits children recursively for given ReactNode root.
4 | // If given f returns true, it will stop walking at the node (no more seeking children recursively).
5 | export const walkChildren = (root: ReactNode, f: (node: ReactNode) => boolean | void): boolean => {
6 | Children.forEach(root, (node) => {
7 | if (f(node)) {
8 | return;
9 | }
10 | if (!isValidElement(node)) {
11 | return;
12 | }
13 | walkChildren(node.props.children, f);
14 | });
15 | return true;
16 | };
17 |
--------------------------------------------------------------------------------
/packages/for-ui/src/table/ColumnDef.ts:
--------------------------------------------------------------------------------
1 | export type { ColumnDef } from '@tanstack/react-table';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/table/TableCell.tsx:
--------------------------------------------------------------------------------
1 | import { FC, forwardRef, HTMLAttributes, ReactNode } from 'react';
2 | import { MdArrowDownward, MdArrowUpward } from 'react-icons/md';
3 | import { ComponentPropsWithAs, Element, Ref } from '../system/componentType';
4 | import { fsx } from '../system/fsx';
5 | import { Text } from '../text';
6 |
7 | export type TableCellProps = ComponentPropsWithAs<
8 | {
9 | /**
10 | * @deprecated `as` propを使ってください
11 | */
12 | component?: 'th' | 'td';
13 |
14 | className?: string;
15 | },
16 | As
17 | >;
18 |
19 | type TableCellComponent = (props: TableCellProps) => Element;
20 |
21 | export const TableCell: TableCellComponent = forwardRef(
22 | ({ component = 'td', as, className, ...rest }: TableCellProps, ref?: Ref) => {
23 | const Component = as || component || 'td';
24 | return (
25 |
37 | );
38 | },
39 | );
40 |
41 | export const SortableTableCellHead: FC<
42 | TableCellProps<'th'> & {
43 | sortable: boolean;
44 | sorted: false | 'asc' | 'desc';
45 | nextSortingOrder: false | 'asc' | 'desc';
46 | children: ReactNode;
47 | disabled?: boolean;
48 | onClick?: HTMLAttributes['onClick'];
49 | }
50 | > = ({ sortable, sorted, nextSortingOrder, onClick, disabled, children, ...rest }) => (
51 |
57 | {sortable ? (
58 |
85 | ) : (
86 | {children}
87 | )}
88 |
89 | );
90 |
--------------------------------------------------------------------------------
/packages/for-ui/src/table/TablePagination.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import { render, screen } from '@testing-library/react';
3 | import userEvent from '@testing-library/user-event';
4 | import { Pagination } from './TablePagination';
5 |
6 | describe('Pagination', () => {
7 | it('is rendered with first, last, prev, next, and current page buttons', async () => {
8 | render();
9 | expect(screen.queryByRole('button', { name: '最初のページへ移動' })).toBeInTheDocument();
10 | expect(screen.queryByRole('button', { name: '最後のページへ移動' })).toBeInTheDocument();
11 | expect(screen.queryByRole('button', { name: '前のページへ移動' })).toBeInTheDocument();
12 | expect(screen.queryByRole('button', { name: '次のページへ移動' })).toBeInTheDocument();
13 | expect(screen.queryByRole('button', { name: 'ページ1へ移動' })).toBeInTheDocument();
14 | expect(screen.queryByRole('button', { name: 'ページ100へ移動' })).toBeInTheDocument();
15 | expect(screen.queryByRole('button', { name: '現在のページ ページ50' })).toBeInTheDocument();
16 | });
17 | it('calls event handlers when first page button is clicked', async () => {
18 | const user = userEvent.setup();
19 | const onChangePage = vi.fn((page: number) => page);
20 | const onClickFirstPageButton = vi.fn();
21 | render(
22 | ,
28 | );
29 | await user.click(screen.getByRole('button', { name: '最初のページへ移動' }));
30 | expect(onChangePage).toHaveBeenCalledOnce();
31 | expect(onChangePage).toHaveLastReturnedWith(1);
32 | expect(onClickFirstPageButton).toHaveBeenCalledOnce();
33 | });
34 | it('calls event handlers when last page button is clicked', async () => {
35 | const user = userEvent.setup();
36 | const onChangePage = vi.fn((page: number) => page);
37 | const onClickLastPageButton = vi.fn();
38 | render(
39 | ,
45 | );
46 | await user.click(screen.getByRole('button', { name: '最後のページへ移動' }));
47 | expect(onChangePage).toHaveBeenCalledOnce();
48 | expect(onChangePage).toHaveLastReturnedWith(100);
49 | expect(onClickLastPageButton).toHaveBeenCalledOnce();
50 | });
51 | it('calls event handlers when previous page button is clicked', async () => {
52 | const user = userEvent.setup();
53 | const onChangePage = vi.fn((page: number) => page);
54 | const onClickPreviousPageButton = vi.fn();
55 | render(
56 | ,
62 | );
63 | await user.click(screen.getByRole('button', { name: '前のページへ移動' }));
64 | expect(onChangePage).toHaveBeenCalledOnce();
65 | expect(onChangePage).toHaveLastReturnedWith(49);
66 | expect(onClickPreviousPageButton).toHaveBeenCalledOnce();
67 | });
68 | it('calls event handlers when next page button is clicked', async () => {
69 | const user = userEvent.setup();
70 | const onChangePage = vi.fn((page: number) => page);
71 | const onClickNextPageButton = vi.fn();
72 | render(
73 | ,
79 | );
80 | await user.click(screen.getByRole('button', { name: '次のページへ移動' }));
81 | expect(onChangePage).toHaveBeenCalledOnce();
82 | expect(onChangePage).toHaveLastReturnedWith(51);
83 | expect(onClickNextPageButton).toHaveBeenCalledOnce();
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/packages/for-ui/src/table/TableScroller.tsx:
--------------------------------------------------------------------------------
1 | import { PropsWithChildren } from 'react';
2 |
3 | type Props = PropsWithChildren<{
4 | height?: number | string;
5 | }>;
6 |
7 | export const TableScroller = ({ height, ...rest }: Props) => (
8 |
14 | );
15 |
--------------------------------------------------------------------------------
/packages/for-ui/src/table/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Table';
2 | export * from './TablePagination';
3 | export * from './TableCell';
4 | export * from './TableScroller';
5 | export * from './ColumnDef';
6 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/Tab.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Meta } from '@storybook/react/types-6-0';
3 | import { Badge } from '../badge';
4 | import { Tab, TabProps } from './Tab';
5 |
6 | export default {
7 | title: 'Navigation / Tabs / Tab',
8 | component: Tab,
9 | } as Meta;
10 |
11 | export const Playground = {
12 | args: {
13 | label: 'ラベル',
14 | badge: ,
15 | disabled: false,
16 | } satisfies TabProps,
17 | };
18 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/Tab.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactElement } from 'react';
2 | import MuiTab, { TabProps as MuiTabProps } from '@mui/material/Tab';
3 | import { fsx } from '../system/fsx';
4 |
5 | export interface TabProps extends Omit {
6 | minWidth?: number;
7 | badge?: ReactElement;
8 | }
9 |
10 | export const Tab: FC = ({ minWidth, tabIndex, className, badge, ...rest }) => (
11 |
23 | );
24 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/TabContext.tsx:
--------------------------------------------------------------------------------
1 | import TabContext, { TabContextProps } from '@mui/lab/TabContext';
2 |
3 | export { TabContext };
4 | export type { TabContextProps };
5 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/TabList.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import MuiTabList, { TabListProps as MuiTabListProps } from '@mui/lab/TabList';
3 | import { fsx } from '../system/fsx';
4 | import { tabWrapperStyle } from './style';
5 |
6 | export interface TabListProps extends MuiTabListProps {
7 | noBorder?: boolean;
8 | reverse?: boolean;
9 | intention?: 'primary' | 'secondary';
10 | }
11 |
12 | export const TabList: FC = ({ intention = 'primary', className, ...rest }) => (
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/TabPanel.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import MuiTabPanel, { TabPanelProps as MuiTabPanelProps } from '@mui/lab/TabPanel';
3 | import { fsx } from '../system/fsx';
4 |
5 | export type TabPanelProps = MuiTabPanelProps;
6 |
7 | export const TabPanel: FC = ({ className, ...rest }) => (
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import MuiTabs, { TabsProps as MuiTabsProps } from '@mui/material/Tabs';
3 | import { fsx } from '../system/fsx';
4 | import { tabWrapperStyle } from './style';
5 |
6 | export interface TabsProps extends MuiTabsProps {
7 | intention?: 'primary' | 'secondary';
8 | }
9 |
10 | export const Tabs: FC = ({ intention = 'primary', className, ...rest }) => (
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Tab';
2 | export * from './TabContext';
3 | export * from './TabPanel';
4 | export * from './Tabs';
5 | export * from './TabList';
6 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tabs/style.ts:
--------------------------------------------------------------------------------
1 | import { fsx } from '../system/fsx';
2 |
3 | export const tabWrapperStyle = (intention: 'primary' | 'secondary') =>
4 | fsx([
5 | `border-shade-light-default h-10 min-h-min w-full overflow-visible border-b border-solid [&_.MuiTabs-indicator]:hidden [&_.MuiTabs-scroller]:h-10`,
6 | {
7 | primary: `[&_[aria-selected=true]]:text-primary-dark-default [&_[aria-selected=true]]:border-b-primary-dark-default`,
8 | secondary: `[&_[aria-selected=true]]:text-secondary-dark-default [&_[aria-selected=true]]:border-b-secondary-dark-default`,
9 | }[intention],
10 | ]);
11 |
--------------------------------------------------------------------------------
/packages/for-ui/src/testing/rhf.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { FieldValues, useForm, UseFormProps, UseFormRegister } from 'react-hook-form';
3 | import { act, fireEvent, screen } from '@testing-library/react';
4 |
5 | export const withRHF = ({
6 | useFormOptions,
7 | onSubmit,
8 | Component,
9 | }: {
10 | useFormOptions?: UseFormProps;
11 | onSubmit: (value: Value) => void;
12 | Component: FC<{ register: UseFormRegister }>;
13 | }): {
14 | Form: FC;
15 | submit: () => Promise;
16 | } => {
17 | return {
18 | Form: (): JSX.Element => {
19 | const { register, handleSubmit } = useForm(useFormOptions);
20 | return (
21 |
24 | );
25 | },
26 | submit: async () => {
27 | await act(async () => {
28 | fireEvent.submit(screen.getByRole('form', { name: 'form' }));
29 | });
30 | },
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/packages/for-ui/src/text/Text.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/react/types-6-0';
2 | import { Text } from './Text';
3 |
4 | export default {
5 | title: 'General / Text',
6 | component: Text,
7 | } as Meta;
8 |
9 | export const Base = {
10 | args: {
11 | children: '「For Design System」で叶えるデザインシステムの夢。',
12 | as: 'span',
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/packages/for-ui/src/text/Text.tsx:
--------------------------------------------------------------------------------
1 | import { ElementType, FC, forwardRef, ReactNode } from 'react';
2 | import { ComponentPropsWithAs, Ref } from '../system/componentType';
3 | import { fsx } from '../system/fsx';
4 |
5 | type WithInherit = T | 'inherit';
6 |
7 | type Size = 'xs' | 's' | 'r' | 'xr' | 'l' | 'xl';
8 |
9 | type Weight = 'regular' | 'bold';
10 |
11 | type Typeface = 'sansSerif' | 'monospaced';
12 |
13 | const style = (size: WithInherit, weight: WithInherit, typeface: WithInherit): string => {
14 | return fsx(
15 | {
16 | inherit: ``,
17 | regular: `font-normal`,
18 | bold: `font-bold`,
19 | }[weight],
20 | {
21 | inherit: ``,
22 | sansSerif: `font-sans`,
23 | monospaced: `font-mono`,
24 | }[typeface],
25 | {
26 | inherit: ``,
27 | xs: `text-xs`,
28 | s: `text-s`,
29 | r: `text-r`,
30 | xr: `text-xr`,
31 | l: `text-l`,
32 | xl: `text-xl`,
33 | }[size],
34 | );
35 | };
36 |
37 | export type TextProps = ComponentPropsWithAs<
38 | {
39 | /**
40 | * テキストのサイズを指定
41 | * @default inherit
42 | */
43 | size?: WithInherit;
44 |
45 | /**
46 | * 表示するテキストのウェイトを指定
47 | * @default inherit
48 | */
49 | weight?: WithInherit;
50 |
51 | /**
52 | * 表示する書体の種別を指定
53 | * @default inherit
54 | */
55 | typeface?: WithInherit;
56 |
57 | className?: string;
58 |
59 | /**
60 | * 文字列またはstrong等のコンポーネント (HTML的にvalidになるようにしてください)
61 | */
62 | children: ReactNode;
63 | },
64 | As
65 | >;
66 |
67 | type TextComponent = (props: TextProps) => ReturnType;
68 |
69 | export const Text: TextComponent = forwardRef(
70 | (
71 | { as, size = 'inherit', weight = 'inherit', typeface = 'inherit', className, children, ...rests }: TextProps,
72 | ref: Ref,
73 | ) => {
74 | const Component = as || 'span';
75 | return (
76 |
77 | {children}
78 |
79 | );
80 | },
81 | );
82 |
--------------------------------------------------------------------------------
/packages/for-ui/src/text/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Text';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/textArea/TextArea.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useForm } from 'react-hook-form';
3 | import * as yup from 'yup';
4 | import { yupResolver } from '@hookform/resolvers/yup';
5 | import { Meta } from '@storybook/react/types-6-0';
6 | import { Button } from '../button/Button';
7 | import { Text } from '../text';
8 | import { TextArea } from './TextArea';
9 |
10 | export default {
11 | title: 'Form / TextArea',
12 | component: TextArea,
13 | } as Meta;
14 |
15 | export const Playground = {
16 | args: {
17 | label: '',
18 | placeholder: '',
19 | },
20 | };
21 |
22 | const schema = yup.object().shape({
23 | email: yup.string().required(),
24 | password: yup.string().required(),
25 | });
26 |
27 | export const Default = (): JSX.Element => {
28 | const {
29 | register,
30 | handleSubmit,
31 | formState: { errors },
32 | } = useForm({
33 | resolver: yupResolver(schema),
34 | });
35 | const onSubmit = (data: unknown) => console.info(data);
36 |
37 | return (
38 |
119 | );
120 | };
121 |
--------------------------------------------------------------------------------
/packages/for-ui/src/textArea/TextArea.test.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithoutRef } from 'react';
2 | import { describe, expect, it, vi } from 'vitest';
3 | import { render, screen } from '@testing-library/react';
4 | import userEvent from '@testing-library/user-event';
5 | import { withRHF } from '../testing/rhf';
6 | import { Text } from '../text/Text';
7 | import { TextArea } from './TextArea';
8 |
9 | describe('TextArea', () => {
10 | it('can be rendered', async () => {
11 | render();
12 | expect(screen.getByRole('textbox', { name: 'TextArea' })).toBeInTheDocument();
13 | });
14 | it('with custom label can be rendered', async () => {
15 | render(
161 | );
162 | };
163 |
164 | export const WithIcon = () => } />;
165 |
--------------------------------------------------------------------------------
/packages/for-ui/src/textField/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './TextField';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tooltip/Tooltip.stories.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import { MdOutlineInfo } from 'react-icons/md';
3 | import { Meta, StoryObj } from '@storybook/react/types-6-0';
4 | import { Tooltip, TooltipFrame, TooltipProps } from './Tooltip';
5 |
6 | type Story = StoryObj;
7 |
8 | const TooltipIcon = forwardRef((props, ref) => (
9 |
10 |
11 |
12 | ));
13 |
14 | export default {
15 | title: 'Feedback / Tooltip',
16 | component: Tooltip,
17 | } as Meta;
18 |
19 | export const Playground: Story = {
20 | args: {
21 | title: 'テキスト',
22 | children: ,
23 | },
24 | };
25 |
26 | export const FrameOnly: Story = {
27 | args: {
28 | title: 'テキスト',
29 | },
30 | render: (props) => ,
31 | };
32 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tooltip/Tooltip.test.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithRef, forwardRef } from 'react';
2 | import { describe, expect, it } from 'vitest';
3 | import { render, screen } from '@testing-library/react';
4 | import userEvent from '@testing-library/user-event';
5 | import { Tooltip } from './Tooltip';
6 |
7 | const Trigger = forwardRef>((props, ref) => (
8 |
9 | ));
10 |
11 | describe('Tooltip', () => {
12 | it('has rendered children', async () => {
13 | render(
14 |
15 | trigger
16 | ,
17 | );
18 | expect(await screen.findByText('trigger')).toBeInTheDocument();
19 | });
20 | it('has children with accessible description', async () => {
21 | render(
22 |
23 | trigger
24 | ,
25 | );
26 | expect(await screen.findByText('trigger')).toHaveAccessibleDescription('description');
27 | });
28 | it('is appeared when focusing trigger', async () => {
29 | const user = userEvent.setup();
30 | render(
31 |
32 | trigger
33 | ,
34 | );
35 | await user.tab();
36 | expect(await screen.findByRole('tooltip')).toBeInTheDocument();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithRef, FC, forwardRef, useId } from 'react';
2 | import MuiTooltip, { TooltipProps as MuiTooltipProps } from '@mui/material/Tooltip';
3 | import { fsx } from '../system/fsx';
4 | import { PropsCascader } from '../system/PropsCascader';
5 | import { Text } from '../text';
6 | import { prepareForSlot } from '../utils/prepareForSlot';
7 |
8 | export type TooltipProps = Pick & {
9 | title: string;
10 | };
11 |
12 | export const TooltipFrame = forwardRef>(
13 | ({ children, ...props }, ref) => (
14 |
21 | {children}
22 |
23 | ),
24 | );
25 |
26 | export const Tooltip: FC = ({ children, ...props }) => {
27 | const internalId = useId();
28 | return (
29 |
49 |
50 | {children}
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/packages/for-ui/src/tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tooltip';
2 |
--------------------------------------------------------------------------------
/packages/for-ui/src/utils/prepareForSlot.tsx:
--------------------------------------------------------------------------------
1 | // Retrieved from https://github.com/mui/material-ui/blob/master/packages/mui-base/src/utils/prepareForSlot.tsx
2 | import * as React from 'react';
3 |
4 | export function prepareForSlot(Component: ComponentType) {
5 | type Props = React.ComponentProps;
6 |
7 | return React.forwardRef(function Slot(props, ref) {
8 | const { ownerState: _, ...other } = props;
9 | return React.createElement(Component, {
10 | ...(other as Props),
11 | ref,
12 | });
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/packages/for-ui/styles/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | @tailwind variants;
5 |
6 | @layer base {
7 | body {
8 | @apply text-shade-dark-default text-r font-sans antialiased;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/for-ui/styles/tailwind.v1.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* Only colors */
3 | /* background */
4 | --background: #f8f8f8;
5 | /* primary */
6 | --primary-dark: #00a2a2;
7 | --primary-main: #00bfbf;
8 | --primary-light: #99e5e5;
9 | --primary-bg: #f2fcfc;
10 |
11 | /* gray */
12 | --gray-high: #0a2c33;
13 | --gray-middle: #546b70;
14 | --gray-low: #6c8085;
15 | --gray-disabled: #b6c0c2;
16 | --gray-bg: #f8f9f9;
17 | --gray-hover: #e7eaeb;
18 |
19 | /* info */
20 | --info-main: #0e75eb;
21 | --info-bg: #e3f2ff;
22 |
23 | /* success */
24 | --success-dark: #1ab17e;
25 | --success-main: #20d99a;
26 | --success-bg: #d9fdf0;
27 |
28 | /* warning */
29 | --warning-dark: #e4c408;
30 | --warning-main: #f7d928;
31 | --warning-bg: #fffcc8;
32 |
33 | /* warning */
34 | --error-dark: #cd2804;
35 | --error-main: #e62d05;
36 | --error-bg: #fff0f0;
37 | --error-disabled: #fad5cd;
38 |
39 | /* New */
40 |
41 | /* body */
42 | --background-body: #f8f8f8;
43 | /* Text */
44 | --text-high: #081a1a;
45 | --text-middle: #525f5f;
46 | --text-low: #6b7676;
47 | --text-disabled: #b5baba;
48 | --text-accent: #00bfbf;
49 | --text-accent-dark: #00a2a2;
50 | --text-error: #e73812;
51 | --text-info: #0e75eb;
52 | --text-white: #fff;
53 |
54 | /* Background */
55 | --bg-primary-dark: #00a2a2;
56 | --bg-primary-main: #00bfbf;
57 | --bg-primary-light: #99e5e5;
58 | --bg-primary-bg: #f2fcfc;
59 |
60 | --bg-gray-dark: #525f5f;
61 | --bg-gray-main: #6b7676;
62 | --bg-gray-light: #e6e8e8;
63 | --bg-gray-bg: #f8f8f8;
64 |
65 | --bg-error-dark: #d03210;
66 | --bg-error-main: #e73812;
67 | --bg-error-light: #fff2f5;
68 |
69 | /* Border */
70 | --border-high: #6b7676;
71 | --border-middle: #b5baba;
72 | --border-low: #e6e8e8;
73 | --border-bg: #f8f8f8;
74 | --border-accent: #00bfbf;
75 | --border-error: #e73812;
76 | }
77 |
--------------------------------------------------------------------------------
/packages/for-ui/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | important: ':is(#root, #docs-root)',
3 | presets: [require('./tailwind.config.base.js')],
4 | content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/for-ui/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "typeRoots": [],
4 | "sourceMap": true,
5 | "allowJs": false,
6 | "allowSyntheticDefaultImports": true,
7 | "noUnusedParameters": true,
8 | "noImplicitReturns": true,
9 | "moduleResolution": "node",
10 | "esModuleInterop": true,
11 | "resolveJsonModule": true,
12 | "jsx": "preserve",
13 | "module": "esnext",
14 | "target": "esnext",
15 | "strict": true,
16 | "skipLibCheck": true,
17 | "declaration": true,
18 | "declarationDir": "dist/types",
19 | "emitDeclarationOnly": true
20 | },
21 | "include": ["./src", "./types"],
22 | "exclude": ["./node_modules", "./dist", "./src/**/*.stories.tsx", "./src/**/*.test.tsx"]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/for-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "allowJs": false,
5 | "allowSyntheticDefaultImports": true,
6 | "noUnusedParameters": true,
7 | "noImplicitReturns": true,
8 | "moduleResolution": "node",
9 | "esModuleInterop": true,
10 | "resolveJsonModule": true,
11 | "jsx": "preserve",
12 | "module": "esnext",
13 | "target": "esnext",
14 | "strict": true,
15 | "skipLibCheck": true,
16 | "declaration": true,
17 | "declarationDir": "dist/types",
18 | "types": ["vitest/globals", "@types/testing-library__jest-dom"],
19 | "noEmit": true
20 | },
21 | "include": ["./src", "./types"],
22 | "exclude": ["./node_modules", "./dist"]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/for-ui/types/react-table-config.d.ts:
--------------------------------------------------------------------------------
1 | import React, { MouseEventHandler } from 'react';
2 | import {
3 | TableInstance,
4 | UseColumnOrderInstanceProps,
5 | UseColumnOrderState,
6 | UseExpandedHooks,
7 | UseExpandedInstanceProps,
8 | UseExpandedOptions,
9 | UseExpandedRowProps,
10 | UseExpandedState,
11 | UseFiltersColumnOptions,
12 | UseFiltersColumnProps,
13 | UseFiltersInstanceProps,
14 | UseFiltersOptions,
15 | UseFiltersState,
16 | UseGlobalFiltersInstanceProps,
17 | UseGlobalFiltersOptions,
18 | UseGlobalFiltersState,
19 | UseGroupByCellProps,
20 | UseGroupByColumnOptions,
21 | UseGroupByColumnProps,
22 | UseGroupByHooks,
23 | UseGroupByInstanceProps,
24 | UseGroupByOptions,
25 | UseGroupByRowProps,
26 | UseGroupByState,
27 | UsePaginationInstanceProps,
28 | UsePaginationOptions,
29 | UsePaginationState,
30 | UseResizeColumnsColumnOptions,
31 | UseResizeColumnsColumnProps,
32 | UseResizeColumnsOptions,
33 | UseResizeColumnsState,
34 | UseRowSelectHooks,
35 | UseRowSelectInstanceProps,
36 | UseRowSelectOptions,
37 | UseRowSelectRowProps,
38 | UseRowSelectState,
39 | UseSortByColumnOptions,
40 | UseSortByColumnProps,
41 | UseSortByHooks,
42 | UseSortByInstanceProps,
43 | UseSortByOptions,
44 | UseSortByState,
45 | } from 'react-table';
46 |
47 | declare module 'react-table' {
48 | export interface UseFlexLayoutInstanceProps> {
49 | totalColumnsMinWidth: number;
50 | }
51 |
52 | export interface UseFlexLayoutColumnProps> {
53 | totalMinWidth: number;
54 | }
55 |
56 | export interface TableOptions>
57 | extends UseExpandedOptions,
58 | UseFiltersOptions,
59 | UseFiltersOptions,
60 | UseGlobalFiltersOptions,
61 | UseGroupByOptions,
62 | UsePaginationOptions,
63 | UseResizeColumnsOptions,
64 | UseRowSelectOptions,
65 | UseSortByOptions {}
66 |
67 | export interface Hooks = Record>
68 | extends UseExpandedHooks,
69 | UseGroupByHooks,
70 | UseRowSelectHooks,
71 | UseSortByHooks {}
72 |
73 | export interface TableInstance = Record>
74 | extends UseColumnOrderInstanceProps,
75 | UseExpandedInstanceProps,
76 | UseFiltersInstanceProps,
77 | UseGlobalFiltersInstanceProps,
78 | UseGroupByInstanceProps,
79 | UsePaginationInstanceProps,
80 | UseRowSelectInstanceProps,
81 | UseFlexLayoutInstanceProps,
82 | UsePaginationInstanceProps,
83 | UseSortByInstanceProps {}
84 |
85 | export interface TableState = Record>
86 | extends UseColumnOrderState,
87 | UseExpandedState,
88 | UseFiltersState,
89 | UseGlobalFiltersState,
90 | UseGroupByState,
91 | UsePaginationState,
92 | UseResizeColumnsState,
93 | UseRowSelectState,
94 | UseSortByState {
95 | rowCount: number;
96 | }
97 |
98 | export interface ColumnInterface = Record>
99 | extends UseFiltersColumnOptions,
100 | UseGroupByColumnOptions,
101 | UseResizeColumnsColumnOptions,
102 | UseSortByColumnOptions {
103 | align?: string;
104 | }
105 |
106 | export interface ColumnInstance = Record>
107 | extends UseFiltersColumnProps,
108 | UseGroupByColumnProps,
109 | UseResizeColumnsColumnProps,
110 | UseFlexLayoutColumnProps,
111 | UseSortByColumnProps {}
112 |
113 | export type Cell = Record> = UseGroupByCellProps;
114 |
115 | export interface Row
116 | extends UseExpandedRowProps,
117 | UseGroupByRowProps,
118 | UseRowSelectRowProps {}
119 | }
120 |
121 | export type TableMouseEventHandler = (instance: TableInstance) => MouseEventHandler;
122 |
--------------------------------------------------------------------------------
/packages/for-ui/types/react-table.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable unused-imports/no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-unused-vars */
3 | import '@tanstack/react-table'
4 |
5 | declare module '@tanstack/table-core' {
6 | interface ColumnMeta {
7 | width?: string
8 | minWidth?: string
9 | maxWidth?: string
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/for-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { defineConfig, PluginOption } from 'vite';
3 | import react from '@vitejs/plugin-react';
4 | import pkg from './package.json';
5 |
6 | const externalPackages = Object.keys(pkg.peerDependencies || {});
7 |
8 | const regexesOfPackages = externalPackages.map((packageName) => new RegExp(`^${packageName}(/.*)?`));
9 |
10 | export default defineConfig(({ mode }) => ({
11 | plugins: [react() as PluginOption],
12 | build: {
13 | sourcemap: mode === 'production' ? false : 'inline',
14 | lib: {
15 | entry: path.resolve(__dirname, 'src/index.ts'),
16 | name: 'for-ui',
17 | fileName: (format) => `for-ui${format === 'esm' ? '.esm' : ''}.js`,
18 | },
19 | rollupOptions: {
20 | external: regexesOfPackages,
21 | output: [
22 | {
23 | preserveModulesRoot: 'src',
24 | exports: 'named',
25 | sourcemap: mode === 'production' ? false : 'inline',
26 | dir: 'dist/commonjs',
27 | format: 'cjs',
28 | },
29 | {
30 | preserveModulesRoot: 'src',
31 | sourcemap: mode === 'production' ? false : 'inline',
32 | exports: 'named',
33 | dir: 'dist/esm',
34 | format: 'esm',
35 | },
36 | ],
37 | },
38 | },
39 | }));
40 |
--------------------------------------------------------------------------------
/packages/for-ui/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vite';
2 | import { defineConfig } from 'vitest/config';
3 | import react from '@vitejs/plugin-react';
4 | import viteConfig from './vite.config';
5 |
6 | // eslint-disable-next-line import/no-default-export
7 | export default mergeConfig(
8 | viteConfig,
9 | defineConfig({
10 | plugins: [react()],
11 | test: {
12 | environment: 'jsdom',
13 | globals: true, // TODO: globals should be false, but cannot resolve `ReferenceError: expect is not defined` error.
14 | setupFiles: ['./vitest.setup.ts'],
15 | },
16 | }),
17 | );
18 |
--------------------------------------------------------------------------------
/packages/for-ui/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'vitest';
2 | import '@testing-library/jest-dom';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import matchers from '@testing-library/jest-dom/matchers';
5 |
6 | expect.extend(matchers);
7 |
--------------------------------------------------------------------------------
/packages/prettier-config/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @3design/prettier-config
2 |
3 | ## 1.0.9
4 |
5 | ### Patch Changes
6 |
7 | - [#1428](https://github.com/4-design/for-ui/pull/1428) [`e8d6986`](https://github.com/4-design/for-ui/commit/e8d69865aaad09b254cd4afaec4e00491a74ebbd) Thanks [@renovate](https://github.com/apps/renovate)! - chore(deps): update dependency prettier to v3.0.1
8 |
9 | ## 1.0.8
10 |
11 | ### Patch Changes
12 |
13 | - [#1379](https://github.com/4-design/for-ui/pull/1379) [`5413c99`](https://github.com/4-design/for-ui/commit/5413c99d072d7fc727e89a2eea90b714baa59771) Thanks [@renovate](https://github.com/apps/renovate)! - chore(deps): update dependency prettier to v3
14 |
15 | - [#1391](https://github.com/4-design/for-ui/pull/1391) [`b22bd20`](https://github.com/4-design/for-ui/commit/b22bd20d736823f7743251c6ff658907b74fddbc) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency prettier-plugin-sh to v0.13.1
16 |
17 | ## 1.0.7
18 |
19 | ### Patch Changes
20 |
21 | - [#1227](https://github.com/4-design/for-ui/pull/1227) [`84a9488`](https://github.com/4-design/for-ui/commit/84a9488d103b37f3ddb1a7cb5dcf0a7c680eeaf0) Thanks [@Qs-F](https://github.com/Qs-F)! - fix: 依存パッケージのアップデート
22 |
23 | ## 1.0.6
24 |
25 | ### Patch Changes
26 |
27 | - [#1073](https://github.com/4-design/for-ui/pull/1073) [`d073e4d`](https://github.com/4-design/for-ui/commit/d073e4d65bfaed589292c0552b1ee1391b754468) Thanks [@locona](https://github.com/locona)! - fix(prettier-config): prettier-plugin-pkg / @trivago/prettier-plugin-sort-imports 追加
28 |
29 | ## 1.0.5
30 |
31 | ### Patch Changes
32 |
33 | - [#1019](https://github.com/4-design/for-ui/pull/1019) [`773949d`](https://github.com/4-design/for-ui/commit/773949dd6dec6d0d9854c2353702a88436a12934) Thanks [@Qs-F](https://github.com/Qs-F)! - fix: Storybook への lint を導入
34 |
35 | ## 1.0.4
36 |
37 | ### Patch Changes
38 |
39 | - [#935](https://github.com/4-design/for-ui/pull/935) [`aaa34c6`](https://github.com/4-design/for-ui/commit/aaa34c66c1ac44d1fcba3347b2d9b46a86af0069) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update all non-major dependencies
40 |
41 | ## 1.0.3
42 |
43 | ### Patch Changes
44 |
45 | - [#917](https://github.com/4-design/for-ui/pull/917) [`86a452f`](https://github.com/4-design/for-ui/commit/86a452fe38215defc82f00263db0e9c83ef0d75e) Thanks [@renovate](https://github.com/apps/renovate)! - chore(deps): update all non-major dependencies
46 |
47 | ## 1.0.2
48 |
49 | ### Patch Changes
50 |
51 | - [#884](https://github.com/4-design/for-ui/pull/884) [`7889f97`](https://github.com/4-design/for-ui/commit/7889f97c01a4df82c2f6fd855393277e197422dd) Thanks [@renovate](https://github.com/apps/renovate)! - chore(deps): update all non-major dependencies
52 |
53 | ## 1.0.1
54 |
55 | ### Patch Changes
56 |
57 | - [#893](https://github.com/4-design/for-ui/pull/893) [`77b4afa`](https://github.com/4-design/for-ui/commit/77b4afafacb7a5c003128e1bfbd2f182697a72b8) Thanks [@Qs-F](https://github.com/Qs-F)! - fix: class が上書きできない問題を解決
58 |
59 | ## 1.0.0
60 |
61 | ### Major Changes
62 |
63 | - [#816](https://github.com/4-design/for-ui/pull/816) [`49256e9`](https://github.com/4-design/for-ui/commit/49256e932b0c5be205ad584496092eaf24e751a8) Thanks [@locona](https://github.com/locona)! - BREAKING CHANGE: パッケージ名変更 @4design/four-ui @4design/eslint-config @4design/prettier-config
64 |
65 | ## 0.0.4
66 |
67 | ### Patch Changes
68 |
69 | - [#814](https://github.com/4-design/four-ui/pull/814) [`afc45a6`](https://github.com/4-design/four-ui/commit/afc45a691c3a8ea92fc797335be8895460802f31) Thanks [@locona](https://github.com/locona)! - fix: https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/155
70 |
71 | ## 0.0.3
72 |
73 | ### Patch Changes
74 |
75 | - [#803](https://github.com/3-shake/3design-ui/pull/803) [`efcd702`](https://github.com/3-shake/3design-ui/commit/efcd702eda9d56b9a796988c7644d1e8f35c14ba) Thanks [@locona](https://github.com/locona)! - fix: https://github.com/francoismassart/eslint-plugin-tailwindcss/isuues/155
76 |
77 | ## 0.0.2
78 |
79 | ### Patch Changes
80 |
81 | - [#761](https://github.com/3-shake/3design-ui/pull/761) [`cb4c7a0`](https://github.com/3-shake/3design-ui/commit/cb4c7a027e626e9a12f42eba0c6439326e3878fc) Thanks [@locona](https://github.com/locona)! - fix: Reset npm version of eslint-config / prettier-config
82 |
83 | ## 0.0.1
84 |
85 | ### Patch Changes
86 |
87 | - [#761](https://github.com/3-shake/3design-ui/pull/761) [`cb4c7a0`](https://github.com/3-shake/3design-ui/commit/cb4c7a027e626e9a12f42eba0c6439326e3878fc) Thanks [@locona](https://github.com/locona)! - fix: Reset npm version of eslint-config / prettier-config
88 |
89 | ## 0.0.1
90 |
91 | ### Patch Changes
92 |
93 | - [#759](https://github.com/3-shake/3design-ui/pull/759) [`01c6f14`](https://github.com/3-shake/3design-ui/commit/01c6f147b8fc8580d8a0c5d08e42a184c587bb60) Thanks [@locona](https://github.com/locona)! - feat: prettier-config 実装
94 |
--------------------------------------------------------------------------------
/packages/prettier-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@4design/prettier-config",
3 | "version": "1.0.9",
4 | "description": "4-design's shared Prettier config",
5 | "repository": {
6 | "url": "4-design/for-ui",
7 | "directory": "packages/prettier-config"
8 | },
9 | "author": "locona (https://github.com/locona)",
10 | "license": "MIT",
11 | "exports": {
12 | ".": "./src/index.cjs"
13 | },
14 | "keywords": [
15 | "prettier",
16 | "prettier-config"
17 | ],
18 | "peerDependencies": {
19 | "prettier": "^2 || ^3.0.0"
20 | },
21 | "dependencies": {
22 | "@trivago/prettier-plugin-sort-imports": "^4.0.0",
23 | "prettier-plugin-pkg": "0.18.0",
24 | "prettier-plugin-sh": "0.13.1"
25 | },
26 | "devDependencies": {
27 | "prettier": "3.2.5"
28 | },
29 | "publishConfig": {
30 | "access": "public"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/prettier-config/src/index.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | trailingComma: 'all', // default to `all` in v3
3 | printWidth: 120,
4 | singleQuote: true,
5 | plugins: [
6 | // for prettifying shellscript, Dockerfile, properties, gitignore, dotenv
7 | require('prettier-plugin-sh'),
8 | // for sort fields in package.json
9 | require('prettier-plugin-pkg'),
10 | // for sorting imports
11 | require('@trivago/prettier-plugin-sort-imports'),
12 | ],
13 | importOrder: [
14 | // TODO: Sort side effects on the top
15 | // See more: https://github.com/trivago/prettier-plugin-sort-imports/issues/110
16 | // React and Next.
17 | '^react(-dom)?$',
18 | '^next(/.*|$)',
19 | // Anything not matched in other groups.
20 | '',
21 | // Things that start with `@` or digit or underscore.
22 | '^(@|\\d|_)',
23 | // Anything that starts with a dot, or multiple dots, and doesn't have the "other files" extensions.
24 | '^(?=\\.+)(.(?!\\.(graphql|css|png|svg|jpe?g|webp|avif|wasm|mp4|webm)))+$',
25 | // Other files with extensions.
26 | '^.+\\.(graphql|css|png|svg|jpe?g|webp|avif|wasm|mp4|webm)$',
27 | ],
28 | importOrderSeparation: false, // import order groups won't be separated by a new line
29 | importOrderSortSpecifiers: true, // sorts the import specifiers alphabetically
30 | importOrderCaseInsensitive: true, // case-insensitive sorting
31 | importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
32 | };
33 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base", ":timezone(Asia/Tokyo)"],
3 | "schedule": ["after 10:30 before 18:00 every weekday except after 13:00 before 14:00"],
4 | "useBaseBranchConfig": "merge"
5 | }
6 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turborepo.org/schema.json",
3 | "pipeline": {
4 | "dev": {
5 | "outputs": []
6 | },
7 | "build": {
8 | "dependsOn": ["^build"],
9 | "outputs": ["dist/**", ".next/**"]
10 | },
11 | "type-check": {
12 | "outputs": []
13 | },
14 | "lint": {
15 | "outputs": []
16 | },
17 | "test": {
18 | "outputs": []
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------