@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,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Json } from './index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Json> = {
|
|
5
|
+
title: 'Json/Json',
|
|
6
|
+
component: Json,
|
|
7
|
+
parameters: { layout: 'padded' },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof Json>;
|
|
12
|
+
|
|
13
|
+
const sample = {
|
|
14
|
+
id: 'usr_1042',
|
|
15
|
+
name: 'Alice Park',
|
|
16
|
+
email: 'alice.park@example.com',
|
|
17
|
+
role: 'engineer',
|
|
18
|
+
team: 'Platform',
|
|
19
|
+
active: true,
|
|
20
|
+
amount: 12340,
|
|
21
|
+
createdAt: '2025-09-12T14:30:00Z',
|
|
22
|
+
tags: ['admin', 'beta', 'oncall'],
|
|
23
|
+
preferences: {
|
|
24
|
+
theme: 'dark',
|
|
25
|
+
notifications: { email: true, push: false, sms: false },
|
|
26
|
+
digest: 'weekly',
|
|
27
|
+
},
|
|
28
|
+
notes: null,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
render: () => <Json value={sample} />,
|
|
33
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ReactJsonView from "@uiw/react-json-view";
|
|
2
|
+
import type { JsonProps } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* JSON tree viewer. Thin wrapper over `@uiw/react-json-view` with our
|
|
6
|
+
* conventions: explicit props, named `is` / `can` / `has` booleans,
|
|
7
|
+
* quote-less keys, text truncation lifted to 300 chars.
|
|
8
|
+
*
|
|
9
|
+
* Theming: uses the underlying library's default palette for v0. Token-backed
|
|
10
|
+
* colors are a follow-up when the visual mismatch against the `ui` palette
|
|
11
|
+
* becomes worth addressing.
|
|
12
|
+
*/
|
|
13
|
+
export const Json = (props: JsonProps) => {
|
|
14
|
+
const {
|
|
15
|
+
value,
|
|
16
|
+
isCollapsed = false,
|
|
17
|
+
collapsedDepth = 1,
|
|
18
|
+
hasSizes = false,
|
|
19
|
+
shortenTextAfterLength = 500,
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
const collapsed = isCollapsed ? collapsedDepth : false;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ReactJsonView
|
|
26
|
+
value={value as object}
|
|
27
|
+
collapsed={collapsed}
|
|
28
|
+
enableClipboard={false}
|
|
29
|
+
displayDataTypes={false}
|
|
30
|
+
displayObjectSize={hasSizes}
|
|
31
|
+
shortenTextAfterLength={shortenTextAfterLength}
|
|
32
|
+
>
|
|
33
|
+
<ReactJsonView.Quote render={() => null} />
|
|
34
|
+
</ReactJsonView>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type { JsonProps };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type JsonProps = {
|
|
2
|
+
/** The data to render. Any JSON-serializable value, plus a few extras the underlying lib handles (Date, Map, Set, BigInt). */
|
|
3
|
+
value: unknown;
|
|
4
|
+
/**
|
|
5
|
+
* Collapse nodes at/beyond `collapsedDepth`. Top-level keys stay visible
|
|
6
|
+
* when depth is >= 1. When false (default), the whole tree is expanded.
|
|
7
|
+
*/
|
|
8
|
+
isCollapsed?: boolean;
|
|
9
|
+
/** Depth at which to collapse when `isCollapsed` is true. Ignored otherwise. Default: 1. */
|
|
10
|
+
collapsedDepth?: number;
|
|
11
|
+
/** Show the copy-to-clipboard icon on each node. Default: false.
|
|
12
|
+
* TODO: remove — implementation hard-codes `enableClipboard={false}`. */
|
|
13
|
+
canCopy?: boolean;
|
|
14
|
+
/** Display the data-type label in front of values (e.g. `string`, `int`). Default: false.
|
|
15
|
+
* TODO: remove — implementation hard-codes `displayDataTypes={false}`. */
|
|
16
|
+
hasTypes?: boolean;
|
|
17
|
+
/** Display the size/length badge on objects and arrays (e.g. `{3}`). Default: true. */
|
|
18
|
+
hasSizes?: boolean;
|
|
19
|
+
/** Truncate string values longer than this many characters. 0 disables truncation. Default: 300. */
|
|
20
|
+
shortenTextAfterLength?: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Text } from '../../Content/Text';
|
|
2
|
+
|
|
3
|
+
export type JsonLeafNodeProps = {
|
|
4
|
+
value: unknown;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Renders one value as a leaf — text only, no JSON tree viewer. Handles
|
|
9
|
+
* the primitives React can't render directly (boolean, null, undefined)
|
|
10
|
+
* and lets strings/numbers flow through as JSX children. Anything else
|
|
11
|
+
* (objects, arrays, Dates, Maps, …) currently falls through to `{value}`
|
|
12
|
+
* — that will throw at render time for non-renderable types and is the
|
|
13
|
+
* placeholder until JsonTable grows specialized renderers for them.
|
|
14
|
+
*/
|
|
15
|
+
export const JsonLeafNode = (props: JsonLeafNodeProps) => {
|
|
16
|
+
const { value } = props;
|
|
17
|
+
|
|
18
|
+
if (value === null) {
|
|
19
|
+
return <Text isMuted>null</Text>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
return <Text isMuted>undefined</Text>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof value === 'boolean') {
|
|
27
|
+
return <>{String(value)}</>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return <>{value}</>;
|
|
31
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { JsonTable } from './index';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof JsonTable> = {
|
|
5
|
+
title: 'Json/JsonTable',
|
|
6
|
+
component: JsonTable,
|
|
7
|
+
parameters: { layout: 'padded' },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof JsonTable>;
|
|
12
|
+
|
|
13
|
+
const sample = {
|
|
14
|
+
id: 'usr_1042',
|
|
15
|
+
name: 'Alice Park',
|
|
16
|
+
email: 'alice.park@example.com',
|
|
17
|
+
role: 'engineer',
|
|
18
|
+
team: 'Platform',
|
|
19
|
+
active: true,
|
|
20
|
+
amount: 12340,
|
|
21
|
+
createdAt: '2025-09-12T14:30:00Z',
|
|
22
|
+
tags: ['admin', 'beta', 'oncall'],
|
|
23
|
+
preferences: {
|
|
24
|
+
theme: 'dark',
|
|
25
|
+
notifications: { email: true, push: false, sms: false },
|
|
26
|
+
digest: 'weekly',
|
|
27
|
+
},
|
|
28
|
+
notes: null,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const PlainObject: Story = {
|
|
32
|
+
render: () => <JsonTable value={sample} />,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const WithTopLevelTitle: Story = {
|
|
36
|
+
name: 'With top-level title',
|
|
37
|
+
render: () => <JsonTable value={sample} title="User profile" />,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const PrimitivesAtTopLevel: Story = {
|
|
41
|
+
name: 'Primitives at top level → JsonLeafNode',
|
|
42
|
+
render: () => (
|
|
43
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
|
44
|
+
<JsonTable value="just a string" />
|
|
45
|
+
<JsonTable value={42} />
|
|
46
|
+
<JsonTable value={true} />
|
|
47
|
+
<JsonTable value={false} />
|
|
48
|
+
<JsonTable value={null} />
|
|
49
|
+
<JsonTable value={undefined} />
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { QuickTable, QuickTableRow, QuickTableCell } from '../../Tables/QuickTable';
|
|
2
|
+
import { JsonLeafNode } from './JsonLeafNode';
|
|
3
|
+
import { isPlainObject } from './utils';
|
|
4
|
+
import type { JsonTableProps } from './types';
|
|
5
|
+
|
|
6
|
+
export const JsonTable = (props: JsonTableProps) => {
|
|
7
|
+
const { value, title } = props;
|
|
8
|
+
|
|
9
|
+
if (isPlainObject(value)) {
|
|
10
|
+
const entries = Object.entries(value);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<QuickTable title={title} hasColumnDividers>
|
|
14
|
+
{entries.map((entry) => {
|
|
15
|
+
const [key, child] = entry;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<QuickTableRow key={key}>
|
|
19
|
+
<QuickTableCell width={1}>{key}</QuickTableCell>
|
|
20
|
+
<QuickTableCell>
|
|
21
|
+
<JsonTable value={child} title={key} />
|
|
22
|
+
</QuickTableCell>
|
|
23
|
+
</QuickTableRow>
|
|
24
|
+
);
|
|
25
|
+
})}
|
|
26
|
+
</QuickTable>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return <JsonLeafNode value={value} />;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type { JsonTableProps };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type JsonValue = any;
|
|
4
|
+
|
|
5
|
+
export type JsonTableProps = {
|
|
6
|
+
/** Any value. JsonTable inspects shape and dispatches to a specialized
|
|
7
|
+
* renderer; primitives and unknown shapes fall back to JsonLeafNode. */
|
|
8
|
+
value: JsonValue;
|
|
9
|
+
/** Optional caption rendered above the QuickTable when value is a plain
|
|
10
|
+
* object. JsonTable also passes the parent key down as `title` on each
|
|
11
|
+
* recursive call, so nested object-valued cells render a labeled table. */
|
|
12
|
+
title?: ReactNode;
|
|
13
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
2
|
+
if (value === null || typeof value !== 'object') return false;
|
|
3
|
+
if (Array.isArray(value)) return false;
|
|
4
|
+
const proto = Object.getPrototypeOf(value);
|
|
5
|
+
return proto === null || proto === Object.prototype;
|
|
6
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Bar } from './index';
|
|
3
|
+
import { Button } from '../../Forms/Button';
|
|
4
|
+
import { Debug } from '../Debug';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Bar> = {
|
|
7
|
+
title: 'Layout/Bar',
|
|
8
|
+
component: Bar,
|
|
9
|
+
args: { title: 'Section title' },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof Bar>;
|
|
14
|
+
|
|
15
|
+
export const TitleOnly: Story = {
|
|
16
|
+
render: (args) => (
|
|
17
|
+
<Debug>
|
|
18
|
+
<Bar {...args} />
|
|
19
|
+
</Debug>
|
|
20
|
+
),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const TitleAndRight: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
title: 'Inbox',
|
|
26
|
+
right: (
|
|
27
|
+
<>
|
|
28
|
+
<Button size="small">Filter</Button>
|
|
29
|
+
<Button size="small" variant="primary">
|
|
30
|
+
New
|
|
31
|
+
</Button>
|
|
32
|
+
</>
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Full: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
title: 'Work items',
|
|
40
|
+
left: (
|
|
41
|
+
<>
|
|
42
|
+
<Button size="small" variant="ghost">
|
|
43
|
+
All
|
|
44
|
+
</Button>
|
|
45
|
+
<Button size="small" variant="ghost">
|
|
46
|
+
Mine
|
|
47
|
+
</Button>
|
|
48
|
+
</>
|
|
49
|
+
),
|
|
50
|
+
right: (
|
|
51
|
+
<>
|
|
52
|
+
<Button size="small">Refresh</Button>
|
|
53
|
+
<Button size="small" variant="primary">
|
|
54
|
+
New item
|
|
55
|
+
</Button>
|
|
56
|
+
</>
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const NoTitle: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
title: undefined,
|
|
64
|
+
left: (
|
|
65
|
+
<Button size="small" variant="ghost">
|
|
66
|
+
Back
|
|
67
|
+
</Button>
|
|
68
|
+
),
|
|
69
|
+
right: (
|
|
70
|
+
<>
|
|
71
|
+
<Button size="small">Cancel</Button>
|
|
72
|
+
<Button size="small" variant="primary">
|
|
73
|
+
Save
|
|
74
|
+
</Button>
|
|
75
|
+
</>
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const LongTitleTruncates: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
title:
|
|
83
|
+
'A very long title that should truncate with an ellipsis rather than push the right-side actions off the bar',
|
|
84
|
+
right: (
|
|
85
|
+
<>
|
|
86
|
+
<Button size="small">Filter</Button>
|
|
87
|
+
<Button size="small" variant="primary">
|
|
88
|
+
New
|
|
89
|
+
</Button>
|
|
90
|
+
</>
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
decorators: [
|
|
94
|
+
(Story) => (
|
|
95
|
+
<div style={{ width: 440 }}>
|
|
96
|
+
<Story />
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
],
|
|
100
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BarProps } from './types';
|
|
2
|
+
import { backgroundStyle } from '../../../tokens';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
|
|
5
|
+
export const Bar = (props: BarProps) => {
|
|
6
|
+
const { title, left, right, background } = props;
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className={styles.bar} style={backgroundStyle('--ui-bar-bg', background)}>
|
|
10
|
+
{title && <span className={styles.title}>{title}</span>}
|
|
11
|
+
{left && <div className={styles.left}>{left}</div>}
|
|
12
|
+
{right && <div className={styles.right}>{right}</div>}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type { BarProps };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.bar {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--ui-space-3);
|
|
5
|
+
padding: var(--ui-space-2) var(--ui-space-3);
|
|
6
|
+
background-color: var(--ui-bar-bg, transparent);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.title {
|
|
10
|
+
font-weight: 500;
|
|
11
|
+
color: var(--ui-foreground);
|
|
12
|
+
/* min-width: 0 lets a flex child shrink below its intrinsic content width
|
|
13
|
+
so the ellipsis actually kicks in when the bar gets narrow. */
|
|
14
|
+
min-width: 0;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
text-overflow: ellipsis;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.left {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--ui-space-2);
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.right {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: var(--ui-space-2);
|
|
31
|
+
/* margin-left: auto pushes the right group to the end regardless of what
|
|
32
|
+
(if anything) sits to its left. */
|
|
33
|
+
margin-left: auto;
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { BackgroundToken } from '../../../tokens';
|
|
3
|
+
|
|
4
|
+
export type BarProps = {
|
|
5
|
+
title?: string;
|
|
6
|
+
left?: ReactNode;
|
|
7
|
+
right?: ReactNode;
|
|
8
|
+
/** Ramp level mapped to `--ui-background-{0..3}`. Defaults to transparent. */
|
|
9
|
+
background?: BackgroundToken;
|
|
10
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Debug } from './index';
|
|
3
|
+
import { Stack } from '../Stack';
|
|
4
|
+
import { Text } from '../../Content/Text';
|
|
5
|
+
import { Heading } from '../../Content/Heading';
|
|
6
|
+
import { Card } from '../../Content/Card';
|
|
7
|
+
import { Repeat } from '../../../storybook';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof Debug> = {
|
|
10
|
+
title: 'Layout/Debug',
|
|
11
|
+
component: Debug,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof Debug>;
|
|
16
|
+
|
|
17
|
+
export const Single: Story = {
|
|
18
|
+
render: () => (
|
|
19
|
+
<Debug>
|
|
20
|
+
<Card>
|
|
21
|
+
<Stack gap={2}>
|
|
22
|
+
<Heading level={3}>Wrapped content</Heading>
|
|
23
|
+
<Text>The outline is drawn outside the debug wrapper's box.</Text>
|
|
24
|
+
<Text isMuted>No layout shift from border math.</Text>
|
|
25
|
+
</Stack>
|
|
26
|
+
</Card>
|
|
27
|
+
</Debug>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Nested: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<Debug>
|
|
34
|
+
<Stack gap={3} padding={3}>
|
|
35
|
+
<Heading level={3}>Outer</Heading>
|
|
36
|
+
<Debug>
|
|
37
|
+
<Stack gap={2} padding={2}>
|
|
38
|
+
<Text>Inner section one — each mount gets its own color.</Text>
|
|
39
|
+
</Stack>
|
|
40
|
+
</Debug>
|
|
41
|
+
<Debug>
|
|
42
|
+
<Stack gap={2} padding={2}>
|
|
43
|
+
<Text>Inner section two.</Text>
|
|
44
|
+
<Debug>
|
|
45
|
+
<Text>Even deeper.</Text>
|
|
46
|
+
</Debug>
|
|
47
|
+
</Stack>
|
|
48
|
+
</Debug>
|
|
49
|
+
</Stack>
|
|
50
|
+
</Debug>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const AcrossAList: Story = {
|
|
55
|
+
render: () => (
|
|
56
|
+
<Stack gap={2}>
|
|
57
|
+
<Repeat count={8}>
|
|
58
|
+
{(i) => (
|
|
59
|
+
<Debug>
|
|
60
|
+
<Stack padding={2}>
|
|
61
|
+
<Text>Item {i + 1}</Text>
|
|
62
|
+
</Stack>
|
|
63
|
+
</Debug>
|
|
64
|
+
)}
|
|
65
|
+
</Repeat>
|
|
66
|
+
</Stack>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Recursive: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<Debug isRecursive>
|
|
73
|
+
<Card>
|
|
74
|
+
<Stack gap={2}>
|
|
75
|
+
<Heading level={3}>Recursive outline</Heading>
|
|
76
|
+
<Text>Every descendant gets the same outline color.</Text>
|
|
77
|
+
<Stack direction="row" gap={2}>
|
|
78
|
+
<Text>One</Text>
|
|
79
|
+
<Text>Two</Text>
|
|
80
|
+
<Text isMuted>Three</Text>
|
|
81
|
+
</Stack>
|
|
82
|
+
</Stack>
|
|
83
|
+
</Card>
|
|
84
|
+
</Debug>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState, type CSSProperties } from 'react';
|
|
2
|
+
import type { DebugProps } from './types';
|
|
3
|
+
import { cx } from '../../../utils';
|
|
4
|
+
import styles from './styles.module.css';
|
|
5
|
+
|
|
6
|
+
const HEX_SEEDS = [
|
|
7
|
+
'#e6194B', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
|
|
8
|
+
'#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4',
|
|
9
|
+
'#469990', '#dcbeff', '#9A6324', '#800000', '#aaffc3',
|
|
10
|
+
'#808000', '#ffd8b1', '#000075', '#a9a9a9', '#008080',
|
|
11
|
+
'#e75480', '#5f9ea0', '#ff7f50', '#6b8e23', '#4682b4',
|
|
12
|
+
'#d2691e', '#da70d6', '#708090', '#2e8b57', '#cd5c5c',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// 50% alpha so overlapping frames from nested Debugs blend rather than
|
|
16
|
+
// stack into hard lines.
|
|
17
|
+
const COLORS = HEX_SEEDS.map((hex) => {
|
|
18
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
19
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
20
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
21
|
+
return `rgba(${r}, ${g}, ${b}, 0.5)`;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const Debug = (props: DebugProps) => {
|
|
25
|
+
const { children, isRecursive } = props;
|
|
26
|
+
|
|
27
|
+
// Color is picked once at mount via lazy initializer and held in state
|
|
28
|
+
// so React's concurrent-mode double-invocation of renderers doesn't
|
|
29
|
+
// reroll it on every render.
|
|
30
|
+
const [color] = useState(
|
|
31
|
+
() => COLORS[Math.floor(Math.random() * COLORS.length)],
|
|
32
|
+
);
|
|
33
|
+
const style = { '--debug-color': color } as CSSProperties;
|
|
34
|
+
return (
|
|
35
|
+
<div className={cx(styles.debug, isRecursive && styles.recursive)} style={style}>
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type { DebugProps };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.debug {
|
|
2
|
+
/* outline draws outside the box without taking up layout space, so wrapped
|
|
3
|
+
children render exactly as they would unwrapped — no size shift like a
|
|
4
|
+
border would cause. */
|
|
5
|
+
outline: 1px solid var(--debug-color);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Recursive: apply the same outline to every descendant. All descendants
|
|
9
|
+
share the wrapper's single picked color. If you want per-element rotation,
|
|
10
|
+
wrap individual elements in their own Debug instead. */
|
|
11
|
+
.recursive * {
|
|
12
|
+
outline: 1px solid var(--debug-color);
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type DebugProps = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Frame every descendant element with the same outline color, in addition
|
|
7
|
+
* to the wrapper. Useful for visualizing the layout tree under a single
|
|
8
|
+
* container (e.g. every child of a `Stack` or `Panels` region) without
|
|
9
|
+
* wrapping each one manually.
|
|
10
|
+
*/
|
|
11
|
+
isRecursive?: boolean;
|
|
12
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Divider } from './index';
|
|
3
|
+
import { Stack } from '../Stack';
|
|
4
|
+
import { Text } from '../../Content/Text';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Divider> = {
|
|
7
|
+
title: 'Layout/Divider',
|
|
8
|
+
component: Divider,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof Divider>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<Stack gap={3}>
|
|
17
|
+
<Text>Section above the divider.</Text>
|
|
18
|
+
<Divider />
|
|
19
|
+
<Text>Section below the divider.</Text>
|
|
20
|
+
</Stack>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Grid } from './index';
|
|
3
|
+
import { Card } from '../../Content/Card';
|
|
4
|
+
import { Text } from '../../Content/Text';
|
|
5
|
+
import { Repeat } from '../../../storybook';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Grid> = {
|
|
8
|
+
title: 'Layout/Grid',
|
|
9
|
+
component: Grid,
|
|
10
|
+
args: { columns: 4, gap: 3 },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof Grid>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: (args) => (
|
|
18
|
+
<Grid {...args}>
|
|
19
|
+
<Repeat count={8}>
|
|
20
|
+
{(i) => (
|
|
21
|
+
<Card>
|
|
22
|
+
<Text>Cell {i + 1}</Text>
|
|
23
|
+
</Card>
|
|
24
|
+
)}
|
|
25
|
+
</Repeat>
|
|
26
|
+
</Grid>
|
|
27
|
+
),
|
|
28
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { GridGap, GridProps } from './types';
|
|
3
|
+
import { cx } from '../../../utils';
|
|
4
|
+
import styles from './styles.module.css';
|
|
5
|
+
|
|
6
|
+
const GAP_MAP: Record<GridGap, string> = {
|
|
7
|
+
0: styles.gap0,
|
|
8
|
+
1: styles.gap1,
|
|
9
|
+
2: styles.gap2,
|
|
10
|
+
3: styles.gap3,
|
|
11
|
+
4: styles.gap4,
|
|
12
|
+
5: styles.gap5,
|
|
13
|
+
6: styles.gap6,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const Grid = (props: GridProps) => {
|
|
17
|
+
const { children, columns, gap = 2 } = props;
|
|
18
|
+
|
|
19
|
+
// The columns count is a runtime value, so it's threaded through as a
|
|
20
|
+
// custom property rather than a discrete class-per-count.
|
|
21
|
+
const style = { '--ui-grid-columns': columns } as CSSProperties;
|
|
22
|
+
return (
|
|
23
|
+
<div className={cx(styles.grid, GAP_MAP[gap])} style={style}>
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type { GridProps };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.grid {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: repeat(var(--ui-grid-columns), minmax(0, 1fr));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.gap0 { gap: 0; }
|
|
7
|
+
.gap1 { gap: var(--ui-space-1); }
|
|
8
|
+
.gap2 { gap: var(--ui-space-2); }
|
|
9
|
+
.gap3 { gap: var(--ui-space-3); }
|
|
10
|
+
.gap4 { gap: var(--ui-space-4); }
|
|
11
|
+
.gap5 { gap: var(--ui-space-5); }
|
|
12
|
+
.gap6 { gap: var(--ui-space-6); }
|