@structuralists/scaffolding 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +9 -0
- package/.storybook/manager.ts +13 -0
- package/.storybook/preview.tsx +18 -0
- package/CLAUDE.md +30 -0
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/bun.lock +947 -0
- package/bunfig.toml +2 -0
- package/eslint.config.mjs +106 -0
- package/index.ts +1 -0
- package/package.json +50 -0
- package/src/components/Chat/ChatComposer/ChatComposer.stories.tsx +68 -0
- package/src/components/Chat/ChatComposer/index.tsx +74 -0
- package/src/components/Chat/ChatComposer/styles.module.css +88 -0
- package/src/components/Chat/ChatComposer/types.ts +11 -0
- package/src/components/Chat/ChatMessage/ChatMessage.stories.tsx +111 -0
- package/src/components/Chat/ChatMessage/index.tsx +42 -0
- package/src/components/Chat/ChatMessage/styles.module.css +58 -0
- package/src/components/Chat/ChatMessage/types.ts +14 -0
- package/src/components/Chat/ChatRecipientsHeader/ChatRecipientsHeader.stories.tsx +145 -0
- package/src/components/Chat/ChatRecipientsHeader/index.tsx +29 -0
- package/src/components/Chat/ChatRecipientsHeader/styles.module.css +48 -0
- package/src/components/Chat/ChatRecipientsHeader/types.ts +26 -0
- package/src/components/Chat/ChatShell/ChatShell.stories.tsx +203 -0
- package/src/components/Chat/ChatShell/index.tsx +16 -0
- package/src/components/Chat/ChatShell/styles.module.css +27 -0
- package/src/components/Chat/ChatShell/types.ts +7 -0
- package/src/components/Chat/PillCombobox/PillCombobox.stories.tsx +59 -0
- package/src/components/Chat/PillCombobox/index.tsx +17 -0
- package/src/components/Chat/PillCombobox/styles.module.css +29 -0
- package/src/components/Chat/PillCombobox/types.ts +28 -0
- package/src/components/Chat/PillComboboxCore/Core.tsx +235 -0
- package/src/components/Chat/PillComboboxCore/styles.module.css +79 -0
- package/src/components/Chat/index.ts +12 -0
- package/src/components/Content/Badge/Badge.stories.tsx +31 -0
- package/src/components/Content/Badge/index.tsx +22 -0
- package/src/components/Content/Badge/styles.module.css +25 -0
- package/src/components/Content/Badge/types.ts +7 -0
- package/src/components/Content/Card/Card.stories.tsx +24 -0
- package/src/components/Content/Card/index.tsx +21 -0
- package/src/components/Content/Card/styles.module.css +13 -0
- package/src/components/Content/Card/types.ts +8 -0
- package/src/components/Content/EditableMarkdown/EditableMarkdown.stories.tsx +58 -0
- package/src/components/Content/EditableMarkdown/index.tsx +140 -0
- package/src/components/Content/EditableMarkdown/styles.module.css +221 -0
- package/src/components/Content/EditableMarkdown/types.ts +11 -0
- package/src/components/Content/Heading/Heading.stories.tsx +26 -0
- package/src/components/Content/Heading/index.tsx +20 -0
- package/src/components/Content/Heading/styles.module.css +19 -0
- package/src/components/Content/Heading/types.ts +8 -0
- package/src/components/Content/Link/Link.stories.tsx +21 -0
- package/src/components/Content/Link/index.tsx +19 -0
- package/src/components/Content/Link/styles.module.css +11 -0
- package/src/components/Content/Link/types.ts +8 -0
- package/src/components/Content/List/List.stories.tsx +62 -0
- package/src/components/Content/List/index.tsx +26 -0
- package/src/components/Content/List/styles.module.css +41 -0
- package/src/components/Content/List/types.ts +33 -0
- package/src/components/Content/LoadingContainer/LoadingContainer.stories.tsx +105 -0
- package/src/components/Content/LoadingContainer/index.tsx +36 -0
- package/src/components/Content/LoadingContainer/styles.module.css +54 -0
- package/src/components/Content/LoadingContainer/types.ts +8 -0
- package/src/components/Content/Markdown/Markdown.stories.tsx +39 -0
- package/src/components/Content/Markdown/index.tsx +28 -0
- package/src/components/Content/Markdown/styles.module.css +79 -0
- package/src/components/Content/Markdown/types.ts +8 -0
- package/src/components/Content/Menu/Menu.stories.tsx +186 -0
- package/src/components/Content/Menu/index.tsx +259 -0
- package/src/components/Content/Menu/styles.module.css +103 -0
- package/src/components/Content/Menu/types.ts +25 -0
- package/src/components/Content/Text/Text.stories.tsx +36 -0
- package/src/components/Content/Text/index.tsx +35 -0
- package/src/components/Content/Text/styles.module.css +30 -0
- package/src/components/Content/Text/types.ts +11 -0
- package/src/components/Forms/Button/Button.stories.tsx +40 -0
- package/src/components/Forms/Button/index.tsx +43 -0
- package/src/components/Forms/Button/styles.module.css +67 -0
- package/src/components/Forms/Button/types.ts +16 -0
- package/src/components/Forms/ColorInput/index.tsx +22 -0
- package/src/components/Forms/ColorInput/styles.module.css +19 -0
- package/src/components/Forms/ColorInput/types.ts +12 -0
- package/src/components/Forms/Field/Field.stories.tsx +35 -0
- package/src/components/Forms/Field/index.tsx +17 -0
- package/src/components/Forms/Field/styles.module.css +21 -0
- package/src/components/Forms/Field/types.ts +9 -0
- package/src/components/Forms/IconButton/IconButton.stories.tsx +91 -0
- package/src/components/Forms/IconButton/index.tsx +55 -0
- package/src/components/Forms/IconButton/styles.module.css +61 -0
- package/src/components/Forms/IconButton/types.ts +23 -0
- package/src/components/Forms/Input/Input.stories.tsx +22 -0
- package/src/components/Forms/Input/index.tsx +42 -0
- package/src/components/Forms/Input/styles.module.css +30 -0
- package/src/components/Forms/Input/types.ts +18 -0
- package/src/components/Forms/SearchInput/index.tsx +41 -0
- package/src/components/Forms/SearchInput/styles.module.css +30 -0
- package/src/components/Forms/SearchInput/types.ts +17 -0
- package/src/components/Forms/Select/MultiSelect/MultiSelect.stories.tsx +116 -0
- package/src/components/Forms/Select/MultiSelect/index.tsx +74 -0
- package/src/components/Forms/Select/MultiSelect/types.ts +15 -0
- package/src/components/Forms/Select/SingleSelect/SingleSelect.stories.tsx +174 -0
- package/src/components/Forms/Select/SingleSelect/index.tsx +62 -0
- package/src/components/Forms/Select/SingleSelect/types.ts +12 -0
- package/src/components/Forms/Select/index.ts +4 -0
- package/src/components/Forms/Select/internal/OptionList.tsx +124 -0
- package/src/components/Forms/Select/internal/SelectTrigger.tsx +60 -0
- package/src/components/Forms/Select/internal/styles.module.css +122 -0
- package/src/components/Forms/Textarea/Textarea.stories.tsx +25 -0
- package/src/components/Forms/Textarea/index.tsx +48 -0
- package/src/components/Forms/Textarea/styles.module.css +34 -0
- package/src/components/Forms/Textarea/types.ts +24 -0
- package/src/components/Json/Json/Json.stories.tsx +33 -0
- package/src/components/Json/Json/index.tsx +38 -0
- package/src/components/Json/Json/types.ts +21 -0
- package/src/components/Json/JsonTable/JsonLeafNode.tsx +31 -0
- package/src/components/Json/JsonTable/JsonTable.stories.tsx +52 -0
- package/src/components/Json/JsonTable/index.tsx +33 -0
- package/src/components/Json/JsonTable/types.ts +13 -0
- package/src/components/Json/JsonTable/utils.ts +6 -0
- package/src/components/Layout/Bar/Bar.stories.tsx +100 -0
- package/src/components/Layout/Bar/index.tsx +17 -0
- package/src/components/Layout/Bar/styles.module.css +34 -0
- package/src/components/Layout/Bar/types.ts +10 -0
- package/src/components/Layout/Debug/Debug.stories.tsx +86 -0
- package/src/components/Layout/Debug/index.tsx +41 -0
- package/src/components/Layout/Debug/styles.module.css +13 -0
- package/src/components/Layout/Debug/types.ts +12 -0
- package/src/components/Layout/Divider/Divider.stories.tsx +22 -0
- package/src/components/Layout/Divider/index.tsx +3 -0
- package/src/components/Layout/Divider/styles.module.css +6 -0
- package/src/components/Layout/Grid/Grid.stories.tsx +28 -0
- package/src/components/Layout/Grid/index.tsx +29 -0
- package/src/components/Layout/Grid/styles.module.css +12 -0
- package/src/components/Layout/Grid/types.ts +9 -0
- package/src/components/Layout/Panels/Panels.stories.tsx +287 -0
- package/src/components/Layout/Panels/index.tsx +119 -0
- package/src/components/Layout/Panels/styles.module.css +103 -0
- package/src/components/Layout/Panels/types.ts +36 -0
- package/src/components/Layout/Stack/Stack.stories.tsx +45 -0
- package/src/components/Layout/Stack/index.tsx +73 -0
- package/src/components/Layout/Stack/styles.module.css +41 -0
- package/src/components/Layout/Stack/types.ts +17 -0
- package/src/components/Modals/ConfirmModal/ConfirmModal.stories.tsx +73 -0
- package/src/components/Modals/ConfirmModal/index.tsx +72 -0
- package/src/components/Modals/ConfirmModal/styles.module.css +62 -0
- package/src/components/Modals/ConfirmModal/types.ts +14 -0
- package/src/components/Modals/LargeModal/LargeModal.stories.tsx +75 -0
- package/src/components/Modals/LargeModal/index.tsx +9 -0
- package/src/components/Modals/LargeModal/styles.module.css +6 -0
- package/src/components/Modals/LargeModal/types.ts +18 -0
- package/src/components/Modals/MediumModal/MediumModal.stories.tsx +121 -0
- package/src/components/Modals/MediumModal/MediumModal.test.tsx +48 -0
- package/src/components/Modals/MediumModal/index.tsx +9 -0
- package/src/components/Modals/MediumModal/styles.module.css +5 -0
- package/src/components/Modals/MediumModal/types.ts +18 -0
- package/src/components/Modals/index.ts +3 -0
- package/src/components/Modals/internal/ModalBody.tsx +21 -0
- package/src/components/Modals/internal/ModalFooter.tsx +12 -0
- package/src/components/Modals/internal/ModalHeader.tsx +27 -0
- package/src/components/Modals/internal/ModalShell.tsx +112 -0
- package/src/components/Modals/internal/styles.module.css +141 -0
- package/src/components/Navigation/TabBar/TabBar.stories.tsx +59 -0
- package/src/components/Navigation/TabBar/index.tsx +25 -0
- package/src/components/Navigation/TabBar/styles.module.css +32 -0
- package/src/components/Navigation/TabBar/types.ts +22 -0
- package/src/components/Navigation/VerticalNav/VerticalNav.stories.tsx +41 -0
- package/src/components/Navigation/VerticalNav/index.tsx +25 -0
- package/src/components/Navigation/VerticalNav/styles.module.css +28 -0
- package/src/components/Navigation/VerticalNav/types.ts +19 -0
- package/src/components/Overlays/Popover/Popover.stories.tsx +154 -0
- package/src/components/Overlays/Popover/index.tsx +175 -0
- package/src/components/Overlays/Popover/styles.module.css +59 -0
- package/src/components/Overlays/Popover/types.ts +34 -0
- package/src/components/Overlays/Tooltip/Tooltip.stories.tsx +41 -0
- package/src/components/Overlays/Tooltip/index.tsx +115 -0
- package/src/components/Overlays/Tooltip/styles.module.css +25 -0
- package/src/components/Overlays/Tooltip/types.ts +15 -0
- package/src/components/Primitives/EmptyValue/EmptyValue.stories.tsx +18 -0
- package/src/components/Primitives/EmptyValue/index.tsx +3 -0
- package/src/components/Primitives/EmptyValue/styles.module.css +3 -0
- package/src/components/Primitives/LinedStack/LinedStack.stories.tsx +101 -0
- package/src/components/Primitives/LinedStack/index.tsx +41 -0
- package/src/components/Primitives/LinedStack/styles.module.css +27 -0
- package/src/components/Primitives/LinedStack/types.ts +49 -0
- package/src/components/Primitives/LongText/LongText.stories.tsx +72 -0
- package/src/components/Primitives/LongText/index.tsx +67 -0
- package/src/components/Primitives/LongText/styles.module.css +30 -0
- package/src/components/Primitives/LongText/types.ts +4 -0
- package/src/components/Primitives/Num/Num.stories.tsx +51 -0
- package/src/components/Primitives/Num/index.tsx +37 -0
- package/src/components/Primitives/Num/types.ts +19 -0
- package/src/components/Primitives/Percent/Percent.stories.tsx +48 -0
- package/src/components/Primitives/Percent/index.tsx +15 -0
- package/src/components/Primitives/Percent/types.ts +10 -0
- package/src/components/Primitives/RelativeTime/RelativeTime.stories.tsx +57 -0
- package/src/components/Primitives/RelativeTime/index.tsx +31 -0
- package/src/components/Primitives/RelativeTime/types.ts +3 -0
- package/src/components/Tables/BigTable/BigTable.stories.tsx +367 -0
- package/src/components/Tables/BigTable/CLAUDE.md +118 -0
- package/src/components/Tables/BigTable/columnDefs.tsx +208 -0
- package/src/components/Tables/BigTable/index.tsx +104 -0
- package/src/components/Tables/BigTable/styles.module.css +83 -0
- package/src/components/Tables/BigTable/types.ts +20 -0
- package/src/components/Tables/QuickTable/CLAUDE.md +118 -0
- package/src/components/Tables/QuickTable/QuickTable.stories.tsx +121 -0
- package/src/components/Tables/QuickTable/index.tsx +86 -0
- package/src/components/Tables/QuickTable/internal.tsx +48 -0
- package/src/components/Tables/QuickTable/styles.module.css +65 -0
- package/src/components/Tables/QuickTable/types.ts +40 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +87 -0
- package/src/storybook/CLAUDE.md +35 -0
- package/src/storybook/Composition.stories.tsx +269 -0
- package/src/storybook/Lorem/index.tsx +54 -0
- package/src/storybook/Placeholder/index.tsx +27 -0
- package/src/storybook/Placeholder/styles.module.css +20 -0
- package/src/storybook/Repeat/index.tsx +23 -0
- package/src/storybook/Toggle/index.tsx +29 -0
- package/src/storybook/_StoryUtils.stories.tsx +58 -0
- package/src/storybook/index.ts +4 -0
- package/src/tokens.ts +31 -0
- package/src/utils.test.ts +24 -0
- package/src/utils.ts +2 -0
- package/test-setup.ts +3 -0
- package/tokens.css +323 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { Panels } from './index';
|
|
4
|
+
import { Bar } from '../Bar';
|
|
5
|
+
import { Button } from '../../Forms/Button';
|
|
6
|
+
import { Text } from '../../Content/Text';
|
|
7
|
+
import { Heading } from '../../Content/Heading';
|
|
8
|
+
import { Stack } from '../Stack';
|
|
9
|
+
import { Lorem } from '../../../storybook';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof Panels> = {
|
|
12
|
+
title: 'Layout/Panels',
|
|
13
|
+
component: Panels,
|
|
14
|
+
parameters: { layout: 'fullscreen' },
|
|
15
|
+
decorators: [
|
|
16
|
+
(Story) => (
|
|
17
|
+
<div style={{ height: '100vh' }}>
|
|
18
|
+
<Story />
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof Panels>;
|
|
26
|
+
|
|
27
|
+
const LeftSidebarContent = () => (
|
|
28
|
+
<Stack gap={2}>
|
|
29
|
+
{['Inbox', 'Today', 'Upcoming', 'Archive'].map((label) => (
|
|
30
|
+
<Text key={label}>{label}</Text>
|
|
31
|
+
))}
|
|
32
|
+
</Stack>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const RightSidebarContent = () => (
|
|
36
|
+
<Stack gap={2}>
|
|
37
|
+
<Heading level={3}>Details</Heading>
|
|
38
|
+
<Text isMuted size="small">
|
|
39
|
+
Select something to inspect.
|
|
40
|
+
</Text>
|
|
41
|
+
</Stack>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const LongMain = () => (
|
|
45
|
+
<Stack gap={3}>
|
|
46
|
+
<Heading level={2}>Main content</Heading>
|
|
47
|
+
<Lorem paragraphs={40} as={Text} />
|
|
48
|
+
</Stack>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Inline padding for main/sidebar slot content; Bar handles its own padding
|
|
52
|
+
// so header/footer slots pass a Bar directly instead of going through this.
|
|
53
|
+
const slot = (children: React.ReactNode, width?: number) => (
|
|
54
|
+
<div style={{ padding: 16, ...(width ? { width } : {}) }}>{children}</div>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
export const Full: Story = {
|
|
58
|
+
render: () => (
|
|
59
|
+
<Panels
|
|
60
|
+
header={
|
|
61
|
+
<Bar
|
|
62
|
+
title="Workbench"
|
|
63
|
+
right={
|
|
64
|
+
<Text isMuted size="small">
|
|
65
|
+
v0.1
|
|
66
|
+
</Text>
|
|
67
|
+
}
|
|
68
|
+
/>
|
|
69
|
+
}
|
|
70
|
+
footer={<Bar title="Ready." />}
|
|
71
|
+
leftSidebar={slot(<LeftSidebarContent />, 200)}
|
|
72
|
+
rightSidebar={slot(<RightSidebarContent />, 260)}
|
|
73
|
+
>
|
|
74
|
+
{slot(<LongMain />)}
|
|
75
|
+
</Panels>
|
|
76
|
+
),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const HeaderAndMain: Story = {
|
|
80
|
+
render: () => (
|
|
81
|
+
<Panels header={<Bar title="Workbench" />}>{slot(<LongMain />)}</Panels>
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const LeftSidebarOnly: Story = {
|
|
86
|
+
render: () => (
|
|
87
|
+
<Panels leftSidebar={slot(<LeftSidebarContent />, 200)}>
|
|
88
|
+
{slot(<LongMain />)}
|
|
89
|
+
</Panels>
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const MainOnly: Story = {
|
|
94
|
+
render: () => <Panels>{slot(<LongMain />)}</Panels>,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// rightOverlay slides in from the right, bounded by header/footer so the
|
|
98
|
+
// app nav and footer remain visible and interactive. Open state lives in
|
|
99
|
+
// the parent — passing `null` triggers the slide-out animation.
|
|
100
|
+
export const RightOverlay: Story = {
|
|
101
|
+
render: () => {
|
|
102
|
+
const RightOverlayDemo = () => {
|
|
103
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Panels
|
|
107
|
+
header={
|
|
108
|
+
<Bar
|
|
109
|
+
title="Workbench"
|
|
110
|
+
right={
|
|
111
|
+
<Text isMuted size="small">
|
|
112
|
+
v0.1
|
|
113
|
+
</Text>
|
|
114
|
+
}
|
|
115
|
+
/>
|
|
116
|
+
}
|
|
117
|
+
footer={<Bar title="Ready." />}
|
|
118
|
+
leftSidebar={slot(<LeftSidebarContent />, 200)}
|
|
119
|
+
rightOverlay={
|
|
120
|
+
selected
|
|
121
|
+
? slot(
|
|
122
|
+
<Stack gap={3}>
|
|
123
|
+
<Heading level={3}>{selected}</Heading>
|
|
124
|
+
<Text isMuted size="small">
|
|
125
|
+
Detail for {selected}. Header and left nav remain
|
|
126
|
+
interactive while this is open.
|
|
127
|
+
</Text>
|
|
128
|
+
<Button onClick={() => setSelected(null)}>Close</Button>
|
|
129
|
+
</Stack>,
|
|
130
|
+
)
|
|
131
|
+
: null
|
|
132
|
+
}
|
|
133
|
+
>
|
|
134
|
+
{slot(
|
|
135
|
+
<Stack gap={3}>
|
|
136
|
+
<Heading level={2}>Items</Heading>
|
|
137
|
+
{['Alpha', 'Beta', 'Gamma'].map((name) => (
|
|
138
|
+
<Button
|
|
139
|
+
key={name}
|
|
140
|
+
onClick={() =>
|
|
141
|
+
setSelected((prev) => (prev === name ? null : name))
|
|
142
|
+
}
|
|
143
|
+
>
|
|
144
|
+
{selected === name ? `Close ${name}` : `Open ${name}`}
|
|
145
|
+
</Button>
|
|
146
|
+
))}
|
|
147
|
+
<Lorem paragraphs={20} as={Text} />
|
|
148
|
+
</Stack>,
|
|
149
|
+
)}
|
|
150
|
+
</Panels>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return <RightOverlayDemo />;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// The overlay's content is itself a Panels — header/footer stay pinned
|
|
159
|
+
// while the body scrolls. The header carries the title and an X close
|
|
160
|
+
// button; the outer layout is intentionally minimal (no sidebars) to
|
|
161
|
+
// keep the focus on the overlay shape.
|
|
162
|
+
const CloseX = ({ onClick }: { onClick: () => void }) => (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
aria-label="Close"
|
|
166
|
+
onClick={onClick}
|
|
167
|
+
style={{
|
|
168
|
+
display: 'inline-flex',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
justifyContent: 'center',
|
|
171
|
+
width: 28,
|
|
172
|
+
height: 28,
|
|
173
|
+
padding: 0,
|
|
174
|
+
background: 'transparent',
|
|
175
|
+
border: 'none',
|
|
176
|
+
borderRadius: 4,
|
|
177
|
+
color: 'var(--ui-muted)',
|
|
178
|
+
cursor: 'pointer',
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
182
|
+
<path
|
|
183
|
+
d="M4 4 L12 12 M12 4 L4 12"
|
|
184
|
+
stroke="currentColor"
|
|
185
|
+
strokeWidth="1.5"
|
|
186
|
+
strokeLinecap="round"
|
|
187
|
+
/>
|
|
188
|
+
</svg>
|
|
189
|
+
</button>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
export const RightOverlayWithNestedPanels: Story = {
|
|
193
|
+
render: () => {
|
|
194
|
+
const Demo = () => {
|
|
195
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Panels
|
|
199
|
+
header={<Bar title="Workbench" />}
|
|
200
|
+
rightOverlayWidth={420}
|
|
201
|
+
rightOverlay={
|
|
202
|
+
isOpen ? (
|
|
203
|
+
<Panels
|
|
204
|
+
header={
|
|
205
|
+
<Bar
|
|
206
|
+
title="Detail"
|
|
207
|
+
right={<CloseX onClick={() => setIsOpen(false)} />}
|
|
208
|
+
/>
|
|
209
|
+
}
|
|
210
|
+
footer={
|
|
211
|
+
<Bar
|
|
212
|
+
right={
|
|
213
|
+
<Button variant="primary" onClick={() => setIsOpen(false)}>
|
|
214
|
+
Done
|
|
215
|
+
</Button>
|
|
216
|
+
}
|
|
217
|
+
/>
|
|
218
|
+
}
|
|
219
|
+
>
|
|
220
|
+
{slot(
|
|
221
|
+
<Stack gap={3}>
|
|
222
|
+
<Heading level={3}>Long content</Heading>
|
|
223
|
+
<Text isMuted size="small">
|
|
224
|
+
Body scrolls; header and footer stay pinned.
|
|
225
|
+
</Text>
|
|
226
|
+
<Lorem paragraphs={30} as={Text} />
|
|
227
|
+
</Stack>,
|
|
228
|
+
)}
|
|
229
|
+
</Panels>
|
|
230
|
+
) : null
|
|
231
|
+
}
|
|
232
|
+
>
|
|
233
|
+
{slot(
|
|
234
|
+
<Stack gap={3}>
|
|
235
|
+
<Heading level={2}>Main</Heading>
|
|
236
|
+
<Button onClick={() => setIsOpen((prev) => !prev)}>
|
|
237
|
+
{isOpen ? 'Close detail' : 'Open detail'}
|
|
238
|
+
</Button>
|
|
239
|
+
</Stack>,
|
|
240
|
+
)}
|
|
241
|
+
</Panels>
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return <Demo />;
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// The "fixed strip above a nested Panels" recipe. The outer Panels' main
|
|
250
|
+
// area hosts a flex column: an intro strip on top (intrinsic height) and
|
|
251
|
+
// a flex-1 area that contains another Panels. The column's `min-height: 0`
|
|
252
|
+
// is what lets the inner Panels resolve its own `height: 100%` without
|
|
253
|
+
// spilling past the outer scroll region.
|
|
254
|
+
//
|
|
255
|
+
// Copy this layout when a detail view needs a non-scrolling header above
|
|
256
|
+
// a list/detail pair that scrolls independently.
|
|
257
|
+
export const NestedFullHeight: Story = {
|
|
258
|
+
render: () => (
|
|
259
|
+
<Panels header={<Bar title="Outer" />}>
|
|
260
|
+
<div
|
|
261
|
+
style={{
|
|
262
|
+
display: 'flex',
|
|
263
|
+
flexDirection: 'column',
|
|
264
|
+
height: '100%',
|
|
265
|
+
minHeight: 0,
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
<div style={{ flex: '0 0 auto', padding: 16 }}>
|
|
269
|
+
<Heading level={3}>Intro strip</Heading>
|
|
270
|
+
<Text size="small" isMuted>
|
|
271
|
+
Fixed height above an inner Panels. Does not scroll.
|
|
272
|
+
</Text>
|
|
273
|
+
</div>
|
|
274
|
+
<div style={{ flex: '1 1 0', minHeight: 0, display: 'flex' }}>
|
|
275
|
+
<div style={{ width: '100%' }}>
|
|
276
|
+
<Panels
|
|
277
|
+
header={<Bar title="Inner" />}
|
|
278
|
+
leftSidebar={slot(<LeftSidebarContent />, 180)}
|
|
279
|
+
>
|
|
280
|
+
{slot(<LongMain />)}
|
|
281
|
+
</Panels>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</Panels>
|
|
286
|
+
),
|
|
287
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useEffect, useState, type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
import { backgroundStyle } from '../../../tokens';
|
|
3
|
+
import { cx } from '../../../utils';
|
|
4
|
+
import type { PanelsProps } from './types';
|
|
5
|
+
import styles from './styles.module.css';
|
|
6
|
+
|
|
7
|
+
// Matches the keyframe duration in styles.module.css. Kept in JS so the
|
|
8
|
+
// overlay stays mounted long enough for the slide-out animation to play.
|
|
9
|
+
const OVERLAY_CLOSE_ANIMATION_MS = 200;
|
|
10
|
+
|
|
11
|
+
export const Panels = (props: PanelsProps) => {
|
|
12
|
+
const {
|
|
13
|
+
header,
|
|
14
|
+
subHeaders,
|
|
15
|
+
footer,
|
|
16
|
+
leftSidebar,
|
|
17
|
+
rightSidebar,
|
|
18
|
+
rightOverlay,
|
|
19
|
+
leftSidebarWidth,
|
|
20
|
+
rightSidebarWidth,
|
|
21
|
+
rightOverlayWidth,
|
|
22
|
+
headerBackground,
|
|
23
|
+
footerBackground,
|
|
24
|
+
leftSidebarBackground,
|
|
25
|
+
rightSidebarBackground,
|
|
26
|
+
mainContentBackground,
|
|
27
|
+
children,
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
const leftSidebarStyle: CSSProperties | undefined =
|
|
31
|
+
leftSidebarWidth != null || leftSidebarBackground != null
|
|
32
|
+
? {
|
|
33
|
+
...(leftSidebarWidth != null ? { width: leftSidebarWidth } : null),
|
|
34
|
+
...(backgroundStyle('--ui-panels-left-bg', leftSidebarBackground) ?? null),
|
|
35
|
+
}
|
|
36
|
+
: undefined;
|
|
37
|
+
|
|
38
|
+
const rightSidebarStyle: CSSProperties | undefined =
|
|
39
|
+
rightSidebarWidth != null || rightSidebarBackground != null
|
|
40
|
+
? {
|
|
41
|
+
...(rightSidebarWidth != null ? { width: rightSidebarWidth } : null),
|
|
42
|
+
...(backgroundStyle('--ui-panels-right-bg', rightSidebarBackground) ?? null),
|
|
43
|
+
}
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
const [renderedOverlay, setRenderedOverlay] = useState<ReactNode>(rightOverlay ?? null);
|
|
47
|
+
const [isOverlayClosing, setIsOverlayClosing] = useState(false);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (rightOverlay != null) {
|
|
51
|
+
setRenderedOverlay(rightOverlay);
|
|
52
|
+
setIsOverlayClosing(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setIsOverlayClosing(true);
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
setRenderedOverlay(null);
|
|
58
|
+
setIsOverlayClosing(false);
|
|
59
|
+
}, OVERLAY_CLOSE_ANIMATION_MS);
|
|
60
|
+
return () => clearTimeout(timer);
|
|
61
|
+
}, [rightOverlay]);
|
|
62
|
+
|
|
63
|
+
const rightOverlayStyle: CSSProperties | undefined =
|
|
64
|
+
rightOverlayWidth != null ? { width: rightOverlayWidth } : undefined;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className={styles.panels}>
|
|
68
|
+
{header && (
|
|
69
|
+
<div
|
|
70
|
+
className={styles.header}
|
|
71
|
+
style={backgroundStyle('--ui-panels-header-bg', headerBackground)}
|
|
72
|
+
>
|
|
73
|
+
{header}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
{subHeaders?.map((child, i) => (
|
|
77
|
+
<div key={i} className={styles.subHeader}>
|
|
78
|
+
{child}
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
<div className={styles.middle}>
|
|
82
|
+
{leftSidebar && (
|
|
83
|
+
<div className={styles.leftSidebar} style={leftSidebarStyle}>
|
|
84
|
+
{leftSidebar}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<div
|
|
88
|
+
className={styles.main}
|
|
89
|
+
style={backgroundStyle('--ui-panels-main-bg', mainContentBackground)}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
{rightSidebar && (
|
|
94
|
+
<div className={styles.rightSidebar} style={rightSidebarStyle}>
|
|
95
|
+
{rightSidebar}
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
{renderedOverlay != null && (
|
|
99
|
+
<div
|
|
100
|
+
className={cx(styles.rightOverlay, isOverlayClosing && styles.closing)}
|
|
101
|
+
style={rightOverlayStyle}
|
|
102
|
+
>
|
|
103
|
+
{renderedOverlay}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
{footer && (
|
|
108
|
+
<div
|
|
109
|
+
className={styles.footer}
|
|
110
|
+
style={backgroundStyle('--ui-panels-footer-bg', footerBackground)}
|
|
111
|
+
>
|
|
112
|
+
{footer}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type { PanelsProps };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
.panels {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100%;
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.header {
|
|
9
|
+
flex-shrink: 0;
|
|
10
|
+
background-color: var(--ui-panels-header-bg, var(--ui-background-2));
|
|
11
|
+
border-bottom: 1px solid var(--ui-border);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.subHeader {
|
|
15
|
+
flex-shrink: 0;
|
|
16
|
+
background-color: var(--ui-background-1);
|
|
17
|
+
border-bottom: 1px solid var(--ui-border);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.footer {
|
|
21
|
+
flex-shrink: 0;
|
|
22
|
+
background-color: var(--ui-panels-footer-bg, var(--ui-background-2));
|
|
23
|
+
border-top: 1px solid var(--ui-border);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.middle {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: row;
|
|
29
|
+
flex: 1 1 0;
|
|
30
|
+
/* min-height: 0 lets flex children establish their own scroll contexts
|
|
31
|
+
instead of stretching the row to their intrinsic content height. */
|
|
32
|
+
min-height: 0;
|
|
33
|
+
/* Anchor for the absolutely-positioned rightOverlay. */
|
|
34
|
+
position: relative;
|
|
35
|
+
/* Clip the rightOverlay during its slide animation so its off-screen
|
|
36
|
+
start/end position can't induce a scrollbar on the page. Inner regions
|
|
37
|
+
(main, sidebars) manage their own overflow. */
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.leftSidebar {
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
overflow: auto;
|
|
44
|
+
background-color: var(--ui-panels-left-bg, var(--ui-background-1));
|
|
45
|
+
border-right: 1px solid var(--ui-border);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.rightSidebar {
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
overflow: auto;
|
|
51
|
+
background-color: var(--ui-panels-right-bg, var(--ui-background-1));
|
|
52
|
+
border-left: 1px solid var(--ui-border);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.main {
|
|
56
|
+
flex: 1 1 0;
|
|
57
|
+
overflow: auto;
|
|
58
|
+
background-color: var(--ui-panels-main-bg, var(--ui-background-0));
|
|
59
|
+
/* min-width: 0 is the row-direction counterpart — without it, overflowing
|
|
60
|
+
main content pushes the layout wider than the viewport. */
|
|
61
|
+
min-width: 0;
|
|
62
|
+
/* Contain z-indexes from descendants (e.g. sticky table headers) so they
|
|
63
|
+
can't paint above the rightOverlay or other chrome that lives in the
|
|
64
|
+
parent stacking context. */
|
|
65
|
+
isolation: isolate;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.rightOverlay {
|
|
69
|
+
position: absolute;
|
|
70
|
+
top: 0;
|
|
71
|
+
bottom: 0;
|
|
72
|
+
right: 0;
|
|
73
|
+
width: 360px;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
overflow: auto;
|
|
77
|
+
background-color: var(--ui-background-0);
|
|
78
|
+
border-left: 1px solid var(--ui-border);
|
|
79
|
+
box-shadow: -4px 0 20px var(--ui-shadow);
|
|
80
|
+
z-index: 1;
|
|
81
|
+
animation: panels-overlay-in 200ms ease-out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.rightOverlay.closing {
|
|
85
|
+
animation: panels-overlay-out 200ms ease-in forwards;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes panels-overlay-in {
|
|
89
|
+
from { transform: translateX(100%); }
|
|
90
|
+
to { transform: translateX(0); }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@keyframes panels-overlay-out {
|
|
94
|
+
from { transform: translateX(0); }
|
|
95
|
+
to { transform: translateX(100%); }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (prefers-reduced-motion: reduce) {
|
|
99
|
+
.rightOverlay,
|
|
100
|
+
.rightOverlay.closing {
|
|
101
|
+
animation: none;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { BackgroundToken } from '../../../tokens';
|
|
3
|
+
|
|
4
|
+
export type PanelsProps = {
|
|
5
|
+
header?: ReactNode;
|
|
6
|
+
/** Optional secondary header strips rendered directly below `header`.
|
|
7
|
+
* Each entry is wrapped in a div with the same divider style so they
|
|
8
|
+
* stack as distinct rows (recipients editor, breadcrumbs, filter bar,
|
|
9
|
+
* etc.). Each entry must carry a `key` prop — `react/jsx-key` will
|
|
10
|
+
* flag any array element without one. */
|
|
11
|
+
subHeaders?: ReactNode[];
|
|
12
|
+
footer?: ReactNode;
|
|
13
|
+
leftSidebar?: ReactNode;
|
|
14
|
+
rightSidebar?: ReactNode;
|
|
15
|
+
/** Transient overlay anchored to the right edge of the middle region,
|
|
16
|
+
* bounded vertically by header/footer so app nav stays visible. Open
|
|
17
|
+
* when truthy, animates out when set back to `null`/`undefined`.
|
|
18
|
+
* Parent owns the open state and renders its own close affordance —
|
|
19
|
+
* there is no backdrop scrim or built-in dismissal. */
|
|
20
|
+
rightOverlay?: ReactNode;
|
|
21
|
+
/** Fixed width for the left sidebar. Numbers → px, strings pass through
|
|
22
|
+
* (`"22rem"`, `"25%"`, etc). Defaults to content-sized. */
|
|
23
|
+
leftSidebarWidth?: number | string;
|
|
24
|
+
/** Fixed width for the right sidebar. Same shape as leftSidebarWidth. */
|
|
25
|
+
rightSidebarWidth?: number | string;
|
|
26
|
+
/** Width of the right overlay. Same shape as the sidebar widths.
|
|
27
|
+
* Defaults to 360px. */
|
|
28
|
+
rightOverlayWidth?: number | string;
|
|
29
|
+
/** Per-region background ramp. Each defaults to the region's stock tone. */
|
|
30
|
+
headerBackground?: BackgroundToken;
|
|
31
|
+
footerBackground?: BackgroundToken;
|
|
32
|
+
leftSidebarBackground?: BackgroundToken;
|
|
33
|
+
rightSidebarBackground?: BackgroundToken;
|
|
34
|
+
mainContentBackground?: BackgroundToken;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Stack } from './index';
|
|
3
|
+
import { Button } from '../../Forms/Button';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Stack> = {
|
|
6
|
+
title: 'Layout/Stack',
|
|
7
|
+
component: Stack,
|
|
8
|
+
argTypes: {
|
|
9
|
+
direction: { control: 'inline-radio', options: ['row', 'column'] },
|
|
10
|
+
gap: { control: { type: 'range', min: 0, max: 6, step: 1 } },
|
|
11
|
+
align: { control: 'inline-radio', options: ['start', 'center', 'end', 'stretch'] },
|
|
12
|
+
justify: { control: 'inline-radio', options: ['start', 'center', 'end', 'between'] },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof Stack>;
|
|
18
|
+
|
|
19
|
+
const demoChildren = (
|
|
20
|
+
<>
|
|
21
|
+
<Button>One</Button>
|
|
22
|
+
<Button>Two</Button>
|
|
23
|
+
<Button>Three</Button>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const Column: Story = {
|
|
28
|
+
args: { direction: 'column', gap: 3 },
|
|
29
|
+
render: (args) => <Stack {...args}>{demoChildren}</Stack>,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Row: Story = {
|
|
33
|
+
args: { direction: 'row', gap: 2 },
|
|
34
|
+
render: (args) => <Stack {...args}>{demoChildren}</Stack>,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const RowSpaceBetween: Story = {
|
|
38
|
+
args: { direction: 'row', justify: 'between' },
|
|
39
|
+
render: (args) => (
|
|
40
|
+
<Stack {...args}>
|
|
41
|
+
<Button>Left</Button>
|
|
42
|
+
<Button>Right</Button>
|
|
43
|
+
</Stack>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
StackAlign,
|
|
3
|
+
StackGap,
|
|
4
|
+
StackJustify,
|
|
5
|
+
StackPadding,
|
|
6
|
+
StackProps,
|
|
7
|
+
} from './types';
|
|
8
|
+
import { cx } from '../../../utils';
|
|
9
|
+
import styles from './styles.module.css';
|
|
10
|
+
|
|
11
|
+
const GAP_MAP: Record<StackGap, string> = {
|
|
12
|
+
0: styles.gap0,
|
|
13
|
+
1: styles.gap1,
|
|
14
|
+
2: styles.gap2,
|
|
15
|
+
3: styles.gap3,
|
|
16
|
+
4: styles.gap4,
|
|
17
|
+
5: styles.gap5,
|
|
18
|
+
6: styles.gap6,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const PADDING_MAP: Record<StackPadding, string> = {
|
|
22
|
+
0: styles.padding0,
|
|
23
|
+
1: styles.padding1,
|
|
24
|
+
2: styles.padding2,
|
|
25
|
+
3: styles.padding3,
|
|
26
|
+
4: styles.padding4,
|
|
27
|
+
5: styles.padding5,
|
|
28
|
+
6: styles.padding6,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ALIGN_MAP: Record<StackAlign, string> = {
|
|
32
|
+
start: styles.alignStart,
|
|
33
|
+
center: styles.alignCenter,
|
|
34
|
+
end: styles.alignEnd,
|
|
35
|
+
stretch: styles.alignStretch,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const JUSTIFY_MAP: Record<StackJustify, string> = {
|
|
39
|
+
start: styles.justifyStart,
|
|
40
|
+
center: styles.justifyCenter,
|
|
41
|
+
end: styles.justifyEnd,
|
|
42
|
+
between: styles.justifyBetween,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Stack = (props: StackProps) => {
|
|
46
|
+
const {
|
|
47
|
+
children,
|
|
48
|
+
direction = 'column',
|
|
49
|
+
gap = 2,
|
|
50
|
+
padding,
|
|
51
|
+
align,
|
|
52
|
+
justify,
|
|
53
|
+
isWrap,
|
|
54
|
+
} = props;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={cx(
|
|
59
|
+
styles.stack,
|
|
60
|
+
direction === 'row' ? styles.row : styles.column,
|
|
61
|
+
GAP_MAP[gap],
|
|
62
|
+
padding !== undefined && PADDING_MAP[padding],
|
|
63
|
+
align && ALIGN_MAP[align],
|
|
64
|
+
justify && JUSTIFY_MAP[justify],
|
|
65
|
+
isWrap && styles.wrap,
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type { StackProps };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.stack {
|
|
2
|
+
display: flex;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.row {
|
|
6
|
+
flex-direction: row;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.column {
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.wrap {
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gap0 { gap: 0; }
|
|
18
|
+
.gap1 { gap: var(--ui-space-1); }
|
|
19
|
+
.gap2 { gap: var(--ui-space-2); }
|
|
20
|
+
.gap3 { gap: var(--ui-space-3); }
|
|
21
|
+
.gap4 { gap: var(--ui-space-4); }
|
|
22
|
+
.gap5 { gap: var(--ui-space-5); }
|
|
23
|
+
.gap6 { gap: var(--ui-space-6); }
|
|
24
|
+
|
|
25
|
+
.padding0 { padding: 0; }
|
|
26
|
+
.padding1 { padding: var(--ui-space-1); }
|
|
27
|
+
.padding2 { padding: var(--ui-space-2); }
|
|
28
|
+
.padding3 { padding: var(--ui-space-3); }
|
|
29
|
+
.padding4 { padding: var(--ui-space-4); }
|
|
30
|
+
.padding5 { padding: var(--ui-space-5); }
|
|
31
|
+
.padding6 { padding: var(--ui-space-6); }
|
|
32
|
+
|
|
33
|
+
.alignStart { align-items: flex-start; }
|
|
34
|
+
.alignCenter { align-items: center; }
|
|
35
|
+
.alignEnd { align-items: flex-end; }
|
|
36
|
+
.alignStretch { align-items: stretch; }
|
|
37
|
+
|
|
38
|
+
.justifyStart { justify-content: flex-start; }
|
|
39
|
+
.justifyCenter { justify-content: center; }
|
|
40
|
+
.justifyEnd { justify-content: flex-end; }
|
|
41
|
+
.justifyBetween { justify-content: space-between; }
|