Skip to content

Commit

Permalink
Merge pull request #5 from fortanix/feature/modal
Browse files Browse the repository at this point in the history
Modal Component
  • Loading branch information
mkrause authored Oct 11, 2024
2 parents cbe5d33 + b05f73e commit f6eb9de
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 125 deletions.
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 = {
render: () => (
<ModalWithSpinnerTrigger>
<Spinner size="large" />
<ModalWithSpinnerTrigger unstyled={true}>
<Modal.Content>
<Spinner size="large" />
</Modal.Content>
</ModalWithSpinnerTrigger>
),
play: async ({ canvasElement }) => {},
};
Loading

0 comments on commit f6eb9de

Please sign in to comment.