Skip to content

Commit

Permalink
add MessageModal
Browse files Browse the repository at this point in the history
  • Loading branch information
takanorip committed Sep 25, 2023
1 parent 7984121 commit 15aa178
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 89 deletions.
6 changes: 3 additions & 3 deletions .scaffdog/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ export const {{ inputs.name | pascal }}: FC<Props> = () => {
# `stories/{{ inputs.name | pascal }}.stories.tsx`

```typescript
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { {{ inputs.name | pascal }} } from '../';

export default {
component: {{ inputs.name | pascal }},
} as ComponentMeta<typeof {{ inputs.name | pascal }}>;
} as Meta<typeof {{ inputs.name | pascal }}>;

const defaultArgs = {};

export const Default: ComponentStoryObj<typeof {{ inputs.name | pascal }}> = {
export const Default: StoryObj<typeof {{ inputs.name | pascal }}> = {
render: (args) => <{{ inputs.name | pascal }} {...args} />,
args: defaultArgs,
};
Expand Down
8 changes: 4 additions & 4 deletions src/components/ActionModal/ActionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Dialog, Transition } from '@headlessui/react';
import clsx from 'clsx';
import { FC, Fragment, PropsWithChildren } from 'react';
import styles from './ActionModal.module.scss';
import { Button } from '~/components/common/Button';
import { AllOrNone } from '~/types';
import { opacityToClassName } from '~/utils/style';
import styles from './ActionModal.module.css';
import { Button } from '../../';
import { opacityToClassName } from '../../utils/style';
import { AllOrNone } from '../../utils/types';

type Opacity = 'normal' | 'darker';

Expand Down
100 changes: 100 additions & 0 deletions src/components/MessageModal/MessageModal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: var(--z-index-modal);
overflow-y: auto;
}

.overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

.normalOverlay {
background: rgb(0 0 0 / 50%);
}

.darkerOverlay {
background: rgb(0 0 0 / 80%);
}

.modalContainer {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}

.modalBody {
position: relative;
display: flex;
flex-direction: column;
gap: var(--size-spacing-lg);
width: calc(100% - 32px);
max-width: 450px;
max-height: 90vh;
padding: 40px 16px 24px;
margin: 0 auto;
overflow-y: auto;
background: #fff;
border-radius: 12px;
}

.modalBody.fullscreen {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100vh;
max-width: 100vh;
height: 100vh;
max-height: 100vh;
padding: calc(env(safe-area-inset-top) + 40px) 32px calc(env(safe-area-inset-bottom) + 24px) 32px;
margin: 0;
overflow-y: hidden;
border-radius: 0;
}

.header {
font-size: var(--text-heading-xs-size);
font-weight: bold;
line-height: var(--text-heading-xs-line);
text-align: center;
white-space: pre-wrap;
}

.content {
text-align: center;
}

.panelEnter {
transition-timing-function: ease-out;
transition-duration: 250ms;
transition-property: opacity;
}

.panelEnterFrom {
opacity: 0;
}

.panelEnterTo {
opacity: 1;
}

.panelLeave {
transition-timing-function: ease-in;
transition-duration: 200ms;
}

.panelLeaveFrom {
opacity: 1;
}

.panelLeaveTo {
opacity: 0;
}
57 changes: 57 additions & 0 deletions src/components/MessageModal/MessageModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Dialog, Transition } from '@headlessui/react';
import clsx from 'clsx';
import { FC, Fragment, PropsWithChildren } from 'react';
import styles from './MessageModal.module.css';
import { Button } from '../../';
import { opacityToClassName } from '../../utils/style';

type Opacity = 'normal' | 'darker';

type Props = {
header?: string;
onClose: () => void;
closeLabel?: string;
overlayOpacity?: Opacity;
fullscreen?: boolean;
open?: boolean;
isStatic?: boolean;
} & PropsWithChildren;

export const MessageModal: FC<Props> = ({
header,
children,
onClose,
overlayOpacity = 'normal',
closeLabel = '閉じる',
fullscreen = false,
open = true,
isStatic = false,
}) => {
const opacityClassName = opacityToClassName(overlayOpacity);

return (
<Transition
show={open}
as={Fragment}
enter={styles.panelEnter}
enterFrom={styles.panelEnterFrom}
enterTo={styles.panelEnterTo}
leave={styles.panelLeave}
leaveFrom={styles.panelLeaveFrom}
leaveTo={styles.panelLeaveTo}
>
<Dialog static={isStatic} onClose={onClose} className={styles.modal}>
<div className={styles.modalContainer}>
<Dialog.Overlay className={clsx(styles.overlay, styles[opacityClassName])} />
<div className={clsx(styles.modalBody, fullscreen && styles.fullscreen)}>
{header && <Dialog.Title className={styles.header}>{header}</Dialog.Title>}
<div className={styles.content}>{children}</div>
<Button block onClick={onClose} aria-label={closeLabel}>
{closeLabel}
</Button>
</div>
</div>
</Dialog>
</Transition>
);
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { ActionHalfModal } from './components/ActionHalfModal/ActionHalfModal';
export { MessageModal } from './components/MessageModal/MessageModal';
export { ActionModal } from './components/ActionModal/ActionModal';
export { Button, LinkButton, DummyButton } from './components/Button/Button';
export { Checkbox } from './components/Checkbox/Checkbox';
Expand Down
6 changes: 3 additions & 3 deletions src/stories/ActionHalfModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentProps } from 'react';
import { ActionHalfModal } from '..';

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

const defaultArgs: ComponentProps<typeof ActionHalfModal> = {
open: false,
Expand All @@ -15,7 +15,7 @@ const defaultArgs: ComponentProps<typeof ActionHalfModal> = {
children: 'body',
};

export const Default: ComponentStoryObj<typeof ActionHalfModal> = {
export const Default: StoryObj<typeof ActionHalfModal> = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
render: (args: any) => <ActionHalfModal {...args} />,
args: defaultArgs,
Expand Down
91 changes: 41 additions & 50 deletions src/stories/ActionModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { StoryObj, Meta } from '@storybook/react';
import { useState, ComponentProps } from 'react';
import { ActionModal } from '../';

export default {
title: 'Component/Feedback/ActionModal',
component: ActionModal,
argTypes: {
overlayOpacity: {
control: { type: 'select' },
options: ['normal', 'darker'],
},
},
} as ComponentMeta<typeof ActionModal>;
} as Meta<typeof ActionModal>;

const defaultArgs: Partial<ComponentProps<typeof ActionModal>> = {
header: 'モーダル',
Expand All @@ -21,47 +14,45 @@ const defaultArgs: Partial<ComponentProps<typeof ActionModal>> = {
isStatic: false,
};

const Template: ComponentStory<typeof ActionModal> = (args) => {
const [open, setOpen] = useState(true);

return (
<>
<button type="button" onClick={() => setOpen(true)}>
Open Modal
</button>
<ActionModal {...args} open={open} onPrimaryAction={() => setOpen(false)} onClose={() => setOpen(false)}>
Default
</ActionModal>
</>
);
export const Default: StoryObj<typeof ActionModal> = {
render: (args) => {
const [open, setOpen] = useState(true);

return (
<>
<button type="button" onClick={() => setOpen(true)}>
Open Modal
</button>
<ActionModal {...args} open={open} onPrimaryAction={() => setOpen(false)} onClose={() => setOpen(false)}>
Default
</ActionModal>
</>
);
},
args: defaultArgs,
};

export const Default = Template.bind({});
Default.args = defaultArgs;
Default.storyName = '';

const TemplateSecondary: ComponentStory<typeof ActionModal> = (args) => {
const [open, setOpen] = useState(true);

return (
<>
<button type="button" onClick={() => setOpen(true)}>
Open Modal
</button>
<ActionModal
{...args}
open={open}
onPrimaryAction={() => setOpen(false)}
secondaryActionLabel={'このまま回答を続ける'}
onSecondaryAction={() => setOpen(false)}
onClose={() => setOpen(false)}
>
Default
</ActionModal>
</>
);
export const Secondary: StoryObj<typeof ActionModal> = {
render: (args) => {
const [open, setOpen] = useState(true);

return (
<>
<button type="button" onClick={() => setOpen(true)}>
Open Modal
</button>
<ActionModal
{...args}
open={open}
onPrimaryAction={() => setOpen(false)}
secondaryActionLabel={'このまま回答を続ける'}
onSecondaryAction={() => setOpen(false)}
onClose={() => setOpen(false)}
>
Default
</ActionModal>
</>
);
},
args: defaultArgs,
};

export const ShowSecondary = TemplateSecondary.bind({});
ShowSecondary.args = defaultArgs;
ShowSecondary.storyName = 'Show secondary button';
8 changes: 4 additions & 4 deletions src/stories/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { action } from '@storybook/addon-actions';
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { BlankLinkIcon, UbieIcon } from '@ubie/ubie-icons';
import { Button } from '../';

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

const defaultArgs = {
children: 'ボタン',
disabled: false,
onClick: action('onClick'),
};

export const Default: ComponentStoryObj<typeof Button> = {
export const Default: StoryObj<typeof Button> = {
render: (args) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '48px' }}>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start', gap: '32px' }}>
Expand Down Expand Up @@ -98,7 +98,7 @@ export const Default: ComponentStoryObj<typeof Button> = {
args: defaultArgs,
};

export const Auth: ComponentStoryObj<typeof Button> = {
export const Auth: StoryObj<typeof Button> = {
render: (args) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '48px', width: '300px' }}>
<Button block fixedIcon="default" {...args} variant="authGoogle">
Expand Down
6 changes: 3 additions & 3 deletions src/stories/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { Checkbox } from '../';

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

const defaultArgs = {
children: 'Checkbox',
};

export const Default: ComponentStoryObj<typeof Checkbox> = {
export const Default: StoryObj<typeof Checkbox> = {
render: (args) => <Checkbox {...args} />,
args: defaultArgs,
};
6 changes: 3 additions & 3 deletions src/stories/DummyButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { BlankLinkIcon, UbieIcon } from '@ubie/ubie-icons';
import { DummyButton } from '..';

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

const defaultArgs = {
children: 'ボタン',
};

export const Default: ComponentStoryObj<typeof DummyButton> = {
export const Default: StoryObj<typeof DummyButton> = {
render: (args) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '48px' }}>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start', gap: '32px' }}>
Expand Down
Loading

0 comments on commit 15aa178

Please sign in to comment.