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

Modal Component #5

Merged
merged 13 commits into from
Oct 11, 2024
19 changes: 18 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
{
// https://github.com/Microsoft/vscode-css-languageservice/blob/main/docs/customData.md
// https://stackoverflow.com/questions/42520229/vs-code-and-intellisense-for-css-grid-and-css-modules
"css.customData": [".vscode/custom.css-data.json"]
"css.customData": [".vscode/custom.css-data.json"],

// Editor (code)
"editor.insertSpaces": true, // Insert spaces when pressing Tab
"editor.tabSize": 2,
"editor.detectIndentation": true, // Detect tabSize/insertSpaces automatically when opening a file
"editor.renderWhitespace": "selection", // Render whitespace as visible when selecting text
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 120,
"editor.rulers": [120],
"editor.formatOnSave": false, // Disable auto-format
"editor.formatOnPaste": false,
"editor.comments.ignoreEmptyLines": false,
"files.insertFinalNewline": true, // Insert a newline at the end of the file when saving
"files.trimTrailingWhitespace": false, // Do not trim trailing whitespace
"editor.trimAutoWhitespace": false,
"files.eol": "\n",
"javascript.preferences.quoteStyle": "single",
}
1 change: 1 addition & 0 deletions src/assets/icons/_icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ export const icons = {
'user': {},
'warning': {},
'workflows': {},
'close-x': {},
} as const satisfies Record<string, IconDef>;
6 changes: 6 additions & 0 deletions src/assets/icons/close-x.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 47 additions & 28 deletions src/components/overlays/Modal/Modal.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,80 @@
/* --bk-modal-background-color: color-mix(in srgb, var(--bk-panel-background-color) 80%, transparent); */
--bk-modal-background-color: var(--bk-panel-background-color);

--bk-modal-inset: calc(var(--bk-sizing-3) + var(--bk-sizing-2));

margin: auto;
padding: 0;
width: var(--bk-sizing-9);
max-width: calc(100% - 6px - 2em);
height: fit-content;
min-height: 60%;
max-height: calc(100% - 6px - 2em);
min-height: bk.$spacing-16;
max-width: calc(100% - bk.$spacing-6);
max-height: calc(100% - bk.$spacing-6);

box-shadow: 0 8px 10px 1px rgba(0 0 0 / 14%), 0 3px 14px 2px rgba(0 0 0 / 12%), 0 5px 5px -3px rgba(0 0 0 / 3%);
background-color: var(--bk-modal-background-color);
border-radius: var(--bk-sizing-2);
border-radius: bk.$sizing-2;

display: none; /* flex */
flex-direction: column;

&.bk-modal-small {
// 484px
width: calc(484 * bk.$size-1);
}
&.bk-modal-medium {
// 684px
width: calc(684 * bk.$size-1);
}
&.bk-modal-large {
// 784px
width: calc(784 * bk.$size-1);
}
&.bk-modal-x-large {
// 906px
width: calc(906 * bk.$size-1);
}
&.bk-modal-fullscreen {
width: calc(100% - bk.$spacing-3);
height: calc(100% - bk.$spacing-3);
}
.bk-modal__header {
position: sticky;
top: 0;
padding: var(--bk-sizing-2) var(--bk-modal-inset);
padding: 0;

background: var(--bk-modal-background-color);

--header-shadow-size: calc(var(--bk-sizing-1) / 2);
box-shadow: 0 var(--header-shadow-size) 0 0 rgba(0 0 0 / 12%);
/* Clip everything except the bottom shadow (-1px for weird clipping behavior with scroll) */
clip-path: inset(-1px -1px calc(-1 * var(--header-shadow-size)) -1px);

display: flex;
flex-direction: row;
align-items: baseline;
margin-bottom: bk.$spacing-7;

h1 {
font-size: 1.4rem;
font-weight: 300;
text-transform: uppercase;
font-size: 16px; // do not match bk variable sizes
font-weight: bk.$font-weight-semibold;
}

:nth-child(1 of :global(.action)) {
margin-left: auto;
}
:global(.action) {
align-self: center;
}
}

.bk-modal__close {
position: absolute;
right: bk.$spacing-8;
top: bk.$spacing-8;
z-index: 1;
}
.bk-modal__container {
padding: bk.$spacing-8;
padding-bottom: bk.$spacing-9;
flex-direction: column;
overflow: hidden;
display: flex;
flex: 1;
}
.bk-modal__content {
flex: 1; /* Make sure we cover all available space */
padding: var(--bk-modal-inset);
padding-top: var(--bk-sizing-3);
overflow: auto;
}


/* Variant: slide out */
--modal-slide-out-inset: var(--bk-sizing-3);
&:is(.bk-modal--slide-out-left, .bk-modal--slide-out-right) {
Expand Down Expand Up @@ -107,7 +127,6 @@
}
}


/* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#animating_dialogs */

opacity: 0;
Expand Down Expand Up @@ -142,7 +161,8 @@
overlay var(--transition-time) allow-discrete,
background-color var(--transition-time);
}
.bk-modal:modal::backdrop {
.bk-modal:modal::backdrop,
.bk-modal-spinner::backdrop {
background-color: rgb(0 0 0 / 20%);
backdrop-filter: blur(5px); /* Should be in px, not rem (blur effect should be constant) */
}
Expand All @@ -153,14 +173,13 @@
}

.bk-modal-spinner {
.bk-modal__header {
display: none;
}
outline: none !important; // prevent blue border on Esc pressing

.bk-modal__content {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
}
}
95 changes: 74 additions & 21 deletions src/components/overlays/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type ModalWithTriggerProps = Omit<React.ComponentProps<typeof Modal>, 'active' |
};
const ModalWithTrigger = ({ triggerLabel = 'Open modal', ...modalProps }: ModalWithTriggerProps) => {
const [active, setActive] = React.useState(false);
const onClose = React.useCallback(() => { setActive(false); }, [setActive]);
const onClose = React.useCallback(() => { setActive(false); }, []);
return (
<>
<Button variant="primary" onPress={() => { setActive(true); }}>{triggerLabel}</Button>
Expand All @@ -41,24 +41,76 @@ const ModalWithTrigger = ({ triggerLabel = 'Open modal', ...modalProps }: ModalW
);
};

export const Interactive: Story = {
const reusableModalChildren: React.JSX.Element = (
<>
<Modal.Header>
<h1>Modal title</h1>
</Modal.Header>
<Modal.Content>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do iusmod tempor incididunt ut labore et dolore magna aliqua.</p>

<ModalWithTrigger triggerLabel="Open Submodal">
<Modal.Header>
<h1>Submodal title</h1>
</Modal.Header>
<Modal.Content>
<p>This is a submodal</p>
<OverflowTester/>
</Modal.Content>
</ModalWithTrigger>

<OverflowTester/>
</Modal.Content>
<Modal.Footer>This is a modal footer with eventual action buttons</Modal.Footer>
</>
);

export const ModalSizeSmall: Story = {
render: () => (
<ModalWithTrigger size="small">
{reusableModalChildren}
</ModalWithTrigger>
),
};

export const ModalSizeMedium: Story = {
render: () => (
<ModalWithTrigger size="medium">
{reusableModalChildren}
</ModalWithTrigger>
),
};

export const ModalSizeLarge: Story = {
render: () => (
<ModalWithTrigger size="large">
{reusableModalChildren}
</ModalWithTrigger>
),
};

export const ModalSizeXLarge: Story = {
render: () => (
<ModalWithTrigger size="x-large">
{reusableModalChildren}
</ModalWithTrigger>
),
};

export const ModalSizeFullScreen: Story = {
render: () => (
<ModalWithTrigger size="fullscreen">
{reusableModalChildren}
</ModalWithTrigger>
),
};

export const ModalUncloseable: Story = {
render: () => (
<ModalWithTrigger>
<div className="body-text">
<p>This is a modal</p>

<ModalWithTrigger>
<div className="body-text">
<p>This is a submodal</p>
<OverflowTester/>
</div>
</ModalWithTrigger>

<OverflowTester/>
</div>
<ModalWithTrigger closeable={false}>
{reusableModalChildren}
</ModalWithTrigger>
),
play: async ({ canvasElement }) => {},
};

type ModalWithSpinnerTriggerProps = Omit<React.ComponentProps<typeof Modal>, 'active' | 'onClose'> & {
Expand All @@ -72,20 +124,21 @@ const ModalWithSpinnerTrigger = ({ triggerLabel = 'Open modal with spinner (it w
setActive(false);
}, 5000);
}
const onClose = React.useCallback(() => { setActive(false); }, [setActive]);
const onClose = React.useCallback(() => { setActive(false); }, []);
return (
<>
<Button variant="primary" onPress={onPress}>{triggerLabel}</Button>
<Modal {...modalProps} active={active} onClose={onClose} closeable={false} className={cl['bk-modal-spinner']}/>
<Modal {...modalProps} active={active} onClose={onClose} closeable={false} className={cl['bk-modal-spinner']} size="fullscreen"/>
</>
);
};

export const ModalWithSpinner: Story = {
mkrause marked this conversation as resolved.
Show resolved Hide resolved
render: () => (
<ModalWithSpinnerTrigger>
<Spinner size="large" />
<ModalWithSpinnerTrigger unstyled={true}>
<Modal.Content>
<Spinner size="large" />
</Modal.Content>
</ModalWithSpinnerTrigger>
),
play: async ({ canvasElement }) => {},
};
Loading