@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,221 @@
|
|
|
1
|
+
/* Display mode ------------------------------------------------------------ */
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.editButton {
|
|
8
|
+
position: absolute;
|
|
9
|
+
top: 0;
|
|
10
|
+
right: 0;
|
|
11
|
+
background: transparent;
|
|
12
|
+
border: 1px solid transparent;
|
|
13
|
+
color: var(--ui-muted);
|
|
14
|
+
font-family: var(--ui-font);
|
|
15
|
+
font-size: var(--ui-text-small);
|
|
16
|
+
padding: 4px 8px;
|
|
17
|
+
border-radius: var(--ui-radius);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
line-height: 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.editButton:hover {
|
|
23
|
+
background-color: var(--ui-background-3);
|
|
24
|
+
color: var(--ui-foreground);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.empty {
|
|
28
|
+
color: var(--ui-muted);
|
|
29
|
+
font-style: italic;
|
|
30
|
+
font-size: var(--ui-text-medium);
|
|
31
|
+
line-height: var(--ui-line-height);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Edit mode --------------------------------------------------------------- */
|
|
35
|
+
|
|
36
|
+
.editor {
|
|
37
|
+
border: 1px solid var(--ui-border);
|
|
38
|
+
border-radius: var(--ui-radius);
|
|
39
|
+
background-color: var(--ui-background-0);
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.tabs {
|
|
44
|
+
display: flex;
|
|
45
|
+
border-bottom: 1px solid var(--ui-border);
|
|
46
|
+
background-color: var(--ui-background-2);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tab {
|
|
50
|
+
background: transparent;
|
|
51
|
+
border: none;
|
|
52
|
+
border-bottom: 2px solid transparent;
|
|
53
|
+
padding: 8px 14px;
|
|
54
|
+
font-family: var(--ui-font);
|
|
55
|
+
font-size: var(--ui-text-small);
|
|
56
|
+
font-weight: 500;
|
|
57
|
+
color: var(--ui-muted);
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
/* pull bottom border down 1px to overlap the tab-strip border */
|
|
60
|
+
margin-bottom: -1px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.tab:hover {
|
|
64
|
+
color: var(--ui-foreground);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tabActive {
|
|
68
|
+
color: var(--ui-foreground);
|
|
69
|
+
border-bottom-color: var(--ui-accent);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.body {
|
|
73
|
+
position: relative;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.textarea {
|
|
77
|
+
display: block;
|
|
78
|
+
width: 100%;
|
|
79
|
+
border: none;
|
|
80
|
+
background-color: var(--ui-background-0);
|
|
81
|
+
color: var(--ui-foreground);
|
|
82
|
+
font-family: var(--ui-font);
|
|
83
|
+
font-size: var(--ui-text-medium);
|
|
84
|
+
line-height: var(--ui-line-height);
|
|
85
|
+
padding: var(--ui-space-3);
|
|
86
|
+
resize: vertical;
|
|
87
|
+
min-height: 160px;
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.textarea:focus {
|
|
92
|
+
outline: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.preview {
|
|
96
|
+
padding: var(--ui-space-3);
|
|
97
|
+
min-height: 160px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.hidden {
|
|
101
|
+
display: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.actions {
|
|
105
|
+
display: flex;
|
|
106
|
+
justify-content: flex-end;
|
|
107
|
+
gap: var(--ui-space-2);
|
|
108
|
+
padding: var(--ui-space-2) var(--ui-space-3);
|
|
109
|
+
border-top: 1px solid var(--ui-border);
|
|
110
|
+
background-color: var(--ui-background-1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.actionButton {
|
|
114
|
+
font-family: var(--ui-font);
|
|
115
|
+
font-size: var(--ui-text-small);
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
padding: 6px 12px;
|
|
118
|
+
border-radius: var(--ui-radius);
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
line-height: 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.cancelButton {
|
|
124
|
+
background: transparent;
|
|
125
|
+
border: 1px solid transparent;
|
|
126
|
+
color: var(--ui-foreground);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.cancelButton:hover:not(:disabled) {
|
|
130
|
+
background-color: var(--ui-background-3);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.saveButton {
|
|
134
|
+
background-color: var(--ui-accent);
|
|
135
|
+
color: var(--ui-on-accent);
|
|
136
|
+
border: 1px solid var(--ui-accent);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.saveButton:hover:not(:disabled) {
|
|
140
|
+
background-color: var(--ui-accent-hover);
|
|
141
|
+
border-color: var(--ui-accent-hover);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.actionButton:disabled {
|
|
145
|
+
opacity: 0.5;
|
|
146
|
+
cursor: not-allowed;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Rendered markdown (duplicated from Markdown primitive so this component
|
|
150
|
+
can evolve its typography independently). */
|
|
151
|
+
|
|
152
|
+
.markdown {
|
|
153
|
+
font-size: var(--ui-text-medium);
|
|
154
|
+
line-height: var(--ui-line-height);
|
|
155
|
+
color: var(--ui-foreground);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.markdown :global(h1),
|
|
159
|
+
.markdown :global(h2),
|
|
160
|
+
.markdown :global(h3),
|
|
161
|
+
.markdown :global(h4) {
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
line-height: var(--ui-line-height-tight);
|
|
164
|
+
margin-top: var(--ui-space-4);
|
|
165
|
+
margin-bottom: var(--ui-space-2);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.markdown :global(h1) { font-size: var(--ui-heading-1); }
|
|
169
|
+
.markdown :global(h2) { font-size: var(--ui-heading-2); }
|
|
170
|
+
.markdown :global(h3),
|
|
171
|
+
.markdown :global(h4) { font-size: var(--ui-heading-3); }
|
|
172
|
+
|
|
173
|
+
.markdown :global(p) { margin-bottom: var(--ui-space-3); }
|
|
174
|
+
.markdown :global(p):last-child { margin-bottom: 0; }
|
|
175
|
+
|
|
176
|
+
.markdown :global(ul),
|
|
177
|
+
.markdown :global(ol) {
|
|
178
|
+
margin-bottom: var(--ui-space-3);
|
|
179
|
+
padding-left: 1.25rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.markdown :global(li) { margin-bottom: var(--ui-space-1); }
|
|
183
|
+
|
|
184
|
+
.markdown :global(code) {
|
|
185
|
+
font-family: var(--ui-font-mono);
|
|
186
|
+
font-size: 0.9em;
|
|
187
|
+
background-color: var(--ui-background-2);
|
|
188
|
+
padding: 1px 4px;
|
|
189
|
+
border-radius: var(--ui-radius);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.markdown :global(pre) {
|
|
193
|
+
background-color: var(--ui-background-2);
|
|
194
|
+
padding: var(--ui-space-3);
|
|
195
|
+
border-radius: var(--ui-radius);
|
|
196
|
+
overflow-x: auto;
|
|
197
|
+
margin-bottom: var(--ui-space-3);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.markdown :global(pre code) {
|
|
201
|
+
background-color: transparent;
|
|
202
|
+
padding: 0;
|
|
203
|
+
font-size: var(--ui-text-small);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.markdown :global(a) {
|
|
207
|
+
color: var(--ui-accent);
|
|
208
|
+
text-decoration: underline;
|
|
209
|
+
text-underline-offset: 2px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.markdown :global(a):hover {
|
|
213
|
+
color: var(--ui-accent-hover);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.markdown :global(blockquote) {
|
|
217
|
+
border-left: 3px solid var(--ui-border);
|
|
218
|
+
padding-left: var(--ui-space-3);
|
|
219
|
+
color: var(--ui-muted);
|
|
220
|
+
margin-bottom: var(--ui-space-3);
|
|
221
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type EditableMarkdownProps = {
|
|
2
|
+
value: string;
|
|
3
|
+
onSave: (next: string) => void | Promise<void>;
|
|
4
|
+
onCancel?: () => void;
|
|
5
|
+
/** Default true. When false, renders display mode with no edit affordance. */
|
|
6
|
+
canEdit?: boolean;
|
|
7
|
+
/** Shown in display mode when `value` is empty. */
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
/** Accessible name for the edit control. Used as "Edit {label}". */
|
|
10
|
+
label?: string;
|
|
11
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Heading } from './index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Heading> = {
|
|
5
|
+
title: 'Content/Heading',
|
|
6
|
+
component: Heading,
|
|
7
|
+
argTypes: {
|
|
8
|
+
level: { control: 'inline-radio', options: [1, 2, 3] },
|
|
9
|
+
},
|
|
10
|
+
args: { children: 'Section heading', level: 1 },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof Heading>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {};
|
|
17
|
+
|
|
18
|
+
export const AllLevels: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<>
|
|
21
|
+
<Heading level={1}>Heading level 1</Heading>
|
|
22
|
+
<Heading level={2}>Heading level 2</Heading>
|
|
23
|
+
<Heading level={3}>Heading level 3</Heading>
|
|
24
|
+
</>
|
|
25
|
+
),
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HeadingProps } from './types';
|
|
2
|
+
import { cx } from '../../../utils';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
|
|
5
|
+
const LEVEL_MAP = {
|
|
6
|
+
1: styles.h1,
|
|
7
|
+
2: styles.h2,
|
|
8
|
+
3: styles.h3,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const Heading = (props: HeadingProps) => {
|
|
12
|
+
const { children, level } = props;
|
|
13
|
+
|
|
14
|
+
const Tag = `h${level}` as const;
|
|
15
|
+
return (
|
|
16
|
+
<Tag className={cx(styles.heading, LEVEL_MAP[level])}>{children}</Tag>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type { HeadingProps };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.heading {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: var(--ui-font);
|
|
4
|
+
font-weight: 600;
|
|
5
|
+
line-height: var(--ui-line-height-tight);
|
|
6
|
+
color: var(--ui-foreground);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.h1 {
|
|
10
|
+
font-size: var(--ui-heading-1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.h2 {
|
|
14
|
+
font-size: var(--ui-heading-2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.h3 {
|
|
18
|
+
font-size: var(--ui-heading-3);
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Link } from './index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Link> = {
|
|
5
|
+
title: 'Content/Link',
|
|
6
|
+
component: Link,
|
|
7
|
+
args: { children: 'Go somewhere', href: '#' },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof Link>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {};
|
|
14
|
+
|
|
15
|
+
export const External: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
isExternal: true,
|
|
18
|
+
href: 'https://example.com',
|
|
19
|
+
children: 'Open external site',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LinkProps } from './types';
|
|
2
|
+
import styles from './styles.module.css';
|
|
3
|
+
|
|
4
|
+
export const Link = (props: LinkProps) => {
|
|
5
|
+
const { children, href, isExternal, onClick } = props;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<a
|
|
9
|
+
href={href}
|
|
10
|
+
className={styles.link}
|
|
11
|
+
onClick={onClick}
|
|
12
|
+
{...(isExternal ? { target: '_blank', rel: 'noreferrer noopener' } : {})}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</a>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type { LinkProps };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { List, ListItem } from './index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof List> = {
|
|
5
|
+
title: 'Content/List',
|
|
6
|
+
component: List,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof List>;
|
|
11
|
+
|
|
12
|
+
const items = [
|
|
13
|
+
{ id: 1, name: 'First item', meta: 'Queue' },
|
|
14
|
+
{ id: 2, name: 'A much longer item name that should truncate', meta: 'Active · Plan' },
|
|
15
|
+
{ id: 3, name: 'Third item', meta: 'Done' },
|
|
16
|
+
{ id: 4, name: 'Fourth item', meta: 'Queue' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
render: () => (
|
|
21
|
+
<div style={{ width: '20rem', border: '1px solid #ccc' }}>
|
|
22
|
+
<List>
|
|
23
|
+
{items.map((it) => (
|
|
24
|
+
<ListItem key={it.id} isSelected={it.id === 2}>
|
|
25
|
+
<a href="#" style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
26
|
+
<span>{it.name}</span>
|
|
27
|
+
<span style={{ fontSize: 12, color: '#888' }}>{it.meta}</span>
|
|
28
|
+
</a>
|
|
29
|
+
</ListItem>
|
|
30
|
+
))}
|
|
31
|
+
</List>
|
|
32
|
+
</div>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const WithButtonItems: Story = {
|
|
37
|
+
render: () => (
|
|
38
|
+
<div style={{ width: '20rem', border: '1px solid #ccc' }}>
|
|
39
|
+
<List>
|
|
40
|
+
{items.map((it) => (
|
|
41
|
+
<ListItem key={it.id} isSelected={it.id === 1}>
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
style={{
|
|
45
|
+
display: 'flex',
|
|
46
|
+
flexDirection: 'column',
|
|
47
|
+
gap: 2,
|
|
48
|
+
background: 'transparent',
|
|
49
|
+
border: 0,
|
|
50
|
+
textAlign: 'left',
|
|
51
|
+
font: 'inherit',
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<span>{it.name}</span>
|
|
55
|
+
<span style={{ fontSize: 12, color: '#888' }}>{it.meta}</span>
|
|
56
|
+
</button>
|
|
57
|
+
</ListItem>
|
|
58
|
+
))}
|
|
59
|
+
</List>
|
|
60
|
+
</div>
|
|
61
|
+
),
|
|
62
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cx } from '../../../utils';
|
|
2
|
+
import { backgroundStyle } from '../../../tokens';
|
|
3
|
+
import type { ListProps, ListItemProps } from './types';
|
|
4
|
+
import styles from './styles.module.css';
|
|
5
|
+
|
|
6
|
+
export const List = (props: ListProps) => {
|
|
7
|
+
const { children, background } = props;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<ul className={styles.list} style={backgroundStyle('--ui-list-bg', background)}>
|
|
11
|
+
{children}
|
|
12
|
+
</ul>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ListItem = (props: ListItemProps) => {
|
|
17
|
+
const { children, isSelected } = props;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<li className={cx(styles.item, isSelected && styles.itemSelected)}>
|
|
21
|
+
{children}
|
|
22
|
+
</li>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type { ListProps, ListItemProps };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.list {
|
|
2
|
+
list-style: none;
|
|
3
|
+
padding: 0;
|
|
4
|
+
margin: 0;
|
|
5
|
+
/* Overflow guard: a long child can't push the list (or its scroll
|
|
6
|
+
ancestor) to side-scroll. */
|
|
7
|
+
overflow-x: hidden;
|
|
8
|
+
background-color: var(--ui-list-bg, transparent);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.item {
|
|
12
|
+
list-style: none;
|
|
13
|
+
/* The 4px breathing room so a focused inner element's outline stays
|
|
14
|
+
inside the list's scrolling ancestor instead of getting clipped. */
|
|
15
|
+
padding: 4px;
|
|
16
|
+
border-bottom: 1px solid var(--ui-border-subtle);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Hover + selected paint the full-bleed outer background. The 4px
|
|
20
|
+
padding frames the inner content but doesn't break the fill. */
|
|
21
|
+
.item:hover {
|
|
22
|
+
background-color: var(--ui-background-1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.itemSelected,
|
|
26
|
+
.itemSelected:hover {
|
|
27
|
+
background-color: var(--ui-background-3);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Style the inner clickable. `:where()` drops the specificity of this
|
|
31
|
+
selector to zero so consumer classes on the inner element win — the
|
|
32
|
+
consumer sets layout (display:flex, gap, etc.), we set the reset. */
|
|
33
|
+
:where(.item) > :where(:first-child) {
|
|
34
|
+
display: block;
|
|
35
|
+
padding: var(--ui-space-2) var(--ui-space-3);
|
|
36
|
+
color: inherit;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
min-width: 0;
|
|
41
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { BackgroundToken } from '../../../tokens';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A simple sidebar-style list container. Renders a <ul> with markers off
|
|
6
|
+
* and `overflow-x: hidden` so a long child (a URL, a mono id, etc.) can't
|
|
7
|
+
* push horizontal scroll.
|
|
8
|
+
*/
|
|
9
|
+
export type ListProps = {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
/** Ramp level mapped to `--ui-background-{0..3}`. Defaults to transparent. */
|
|
12
|
+
background?: BackgroundToken;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A row in a List. Owns the outer shell:
|
|
17
|
+
* - 4px padding all around so a focused child's outline doesn't get
|
|
18
|
+
* clipped by an ancestor scroll container
|
|
19
|
+
* - a full-width bottom separator
|
|
20
|
+
* - hover / selected background on the inner clickable
|
|
21
|
+
* - overflow guards (min-width: 0, overflow: hidden) so long content
|
|
22
|
+
* truncates instead of side-scrolling
|
|
23
|
+
*
|
|
24
|
+
* Pass a single direct child — typically a <Link>, <button>, or <div>.
|
|
25
|
+
* The primitive styles that child via `:where()` so the selectors have
|
|
26
|
+
* zero specificity: the consumer's own className wins for layout
|
|
27
|
+
* (display, gap, etc.) while the clickable reset (color: inherit,
|
|
28
|
+
* text-decoration: none, hover bg) still applies.
|
|
29
|
+
*/
|
|
30
|
+
export type ListItemProps = {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
isSelected?: boolean;
|
|
33
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { LoadingContainer } from './index';
|
|
4
|
+
import { Toggle } from '../../../storybook';
|
|
5
|
+
|
|
6
|
+
const Panel = ({ tone, children }: { tone: '0' | '1'; children: React.ReactNode }) => (
|
|
7
|
+
<div
|
|
8
|
+
style={{
|
|
9
|
+
padding: 24,
|
|
10
|
+
background: `var(--ui-background-${tone})`,
|
|
11
|
+
border: '1px solid var(--ui-border)',
|
|
12
|
+
borderRadius: 'var(--ui-radius)',
|
|
13
|
+
}}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const meta: Meta<typeof LoadingContainer> = {
|
|
20
|
+
title: 'Content/LoadingContainer',
|
|
21
|
+
component: LoadingContainer,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof LoadingContainer>;
|
|
26
|
+
|
|
27
|
+
export const Idle: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<div style={{ width: 400 }}>
|
|
30
|
+
<LoadingContainer isLoading={false}>
|
|
31
|
+
<Panel tone="0">
|
|
32
|
+
<p>Some content that's not loading.</p>
|
|
33
|
+
<p>Tab focus flows through normally.</p>
|
|
34
|
+
<button type="button">Focusable</button>
|
|
35
|
+
</Panel>
|
|
36
|
+
</LoadingContainer>
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Loading: Story = {
|
|
42
|
+
render: () => (
|
|
43
|
+
<div style={{ width: 400 }}>
|
|
44
|
+
<LoadingContainer isLoading>
|
|
45
|
+
<Panel tone="0">
|
|
46
|
+
<p>Content is being refreshed.</p>
|
|
47
|
+
<p>Try tabbing — this button is inert.</p>
|
|
48
|
+
<button type="button">Not focusable while loading</button>
|
|
49
|
+
</Panel>
|
|
50
|
+
</LoadingContainer>
|
|
51
|
+
</div>
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Toggleable: Story = {
|
|
56
|
+
render: () => (
|
|
57
|
+
<Toggle>
|
|
58
|
+
{({ isOn, toggle }) => (
|
|
59
|
+
<div style={{ width: 400 }}>
|
|
60
|
+
<div style={{ marginBottom: 16 }}>
|
|
61
|
+
<button type="button" onClick={toggle}>
|
|
62
|
+
Loading: {isOn ? 'loading' : 'idle'}
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
<LoadingContainer isLoading={isOn}>
|
|
66
|
+
<Panel tone="0">
|
|
67
|
+
<p>Flip the button above.</p>
|
|
68
|
+
<button type="button">Focusable when idle</button>
|
|
69
|
+
</Panel>
|
|
70
|
+
</LoadingContainer>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</Toggle>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Nested: Story = {
|
|
78
|
+
render: () => {
|
|
79
|
+
// Two independent booleans — Toggle's render-prop shape would nest awkwardly here.
|
|
80
|
+
const [outer, setOuter] = useState(false);
|
|
81
|
+
const [inner, setInner] = useState(false);
|
|
82
|
+
return (
|
|
83
|
+
<div style={{ width: 500 }}>
|
|
84
|
+
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
|
|
85
|
+
<button type="button" onClick={() => setOuter((v) => !v)}>
|
|
86
|
+
Outer: {outer ? 'loading' : 'idle'}
|
|
87
|
+
</button>
|
|
88
|
+
<button type="button" onClick={() => setInner((v) => !v)}>
|
|
89
|
+
Inner: {inner ? 'loading' : 'idle'}
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
<LoadingContainer isLoading={outer}>
|
|
93
|
+
<Panel tone="1">
|
|
94
|
+
<p>Outer container</p>
|
|
95
|
+
<LoadingContainer isLoading={inner}>
|
|
96
|
+
<Panel tone="0">
|
|
97
|
+
<p>Inner container — suppresses its own overlay when outer is painting.</p>
|
|
98
|
+
</Panel>
|
|
99
|
+
</LoadingContainer>
|
|
100
|
+
</Panel>
|
|
101
|
+
</LoadingContainer>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { LoadingContainerProps } from './types';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tracks whether an ancestor LoadingContainer is already painting an overlay
|
|
7
|
+
* over this subtree. When true, descendants suppress their own overlays —
|
|
8
|
+
* the ancestor's frost and `inert` already cover them.
|
|
9
|
+
*/
|
|
10
|
+
const LoadingAncestorContext = createContext(false);
|
|
11
|
+
|
|
12
|
+
export const LoadingContainer = (props: LoadingContainerProps) => {
|
|
13
|
+
const { children, isLoading, label = 'Loading' } = props;
|
|
14
|
+
|
|
15
|
+
const ancestorIsCovering = useContext(LoadingAncestorContext);
|
|
16
|
+
const shouldPaint = isLoading && !ancestorIsCovering;
|
|
17
|
+
const isCoveredNow = ancestorIsCovering || isLoading;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<LoadingAncestorContext.Provider value={isCoveredNow}>
|
|
21
|
+
<div className={styles.container} aria-busy={isLoading || undefined}>
|
|
22
|
+
<div className={styles.content} inert={shouldPaint || undefined}>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
{shouldPaint && (
|
|
26
|
+
<div className={styles.overlay} role="status">
|
|
27
|
+
<span className={styles.srOnly}>{label}</span>
|
|
28
|
+
<div className={styles.spinner} aria-hidden />
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
</div>
|
|
32
|
+
</LoadingAncestorContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type { LoadingContainerProps };
|