@scaffold-ui-kit/preview 0.0.0-stage → 2.3.0
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/LICENSE +21 -0
- package/README.md +1868 -2
- package/application/host/bootstrap.ts +43 -0
- package/application/host/main.ts +31 -0
- package/application/host/shell-host.ts +47 -0
- package/application/index.html +12 -0
- package/application/lib/base-url.ts +15 -0
- package/application/lib/no-code-block.ts +7 -0
- package/application/lib/no-setup.ts +3 -0
- package/application/lib/no-views.ts +6 -0
- package/application/preview/code/code.html +12 -0
- package/application/preview/code/main.ts +89 -0
- package/application/preview/globals.ts +254 -0
- package/application/preview/html/main.ts +93 -0
- package/application/preview/html/preview.html +12 -0
- package/application/preview/react/main.tsx +146 -0
- package/application/preview/react/preview.html +12 -0
- package/application/preview/svelte/Preview.svelte +104 -0
- package/application/preview/svelte/main.ts +13 -0
- package/application/preview/svelte/preview.html +12 -0
- package/application/preview/svelte/story-props.ts +172 -0
- package/application/preview/view/main.ts +124 -0
- package/application/preview/view/view.html +12 -0
- package/application/preview/vue/main.ts +145 -0
- package/application/preview/vue/preview.html +12 -0
- package/application/shells/charcell/components/Application/index.css +37 -0
- package/application/shells/charcell/components/Application/index.tsx +65 -0
- package/application/shells/charcell/components/Bar/index.css +68 -0
- package/application/shells/charcell/components/Bar/index.tsx +60 -0
- package/application/shells/charcell/components/CodePane/index.css +60 -0
- package/application/shells/charcell/components/CodePane/index.tsx +113 -0
- package/application/shells/charcell/components/ComponentStage/index.css +26 -0
- package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
- package/application/shells/charcell/components/Control/index.css +20 -0
- package/application/shells/charcell/components/Control/index.tsx +118 -0
- package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
- package/application/shells/charcell/components/Description/index.css +6 -0
- package/application/shells/charcell/components/Description/index.tsx +24 -0
- package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
- package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
- package/application/shells/charcell/components/EditorPanel/index.css +48 -0
- package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
- package/application/shells/charcell/components/EventsRow/index.css +42 -0
- package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
- package/application/shells/charcell/components/HeadingRow/index.css +11 -0
- package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
- package/application/shells/charcell/components/Hint/index.css +16 -0
- package/application/shells/charcell/components/Hint/index.tsx +81 -0
- package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
- package/application/shells/charcell/components/KeyHints/index.css +27 -0
- package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
- package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
- package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
- package/application/shells/charcell/components/Markdown/index.tsx +16 -0
- package/application/shells/charcell/components/Navigation/index.css +13 -0
- package/application/shells/charcell/components/Navigation/index.tsx +98 -0
- package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
- package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
- package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
- package/application/shells/charcell/components/PaneStatus/index.css +7 -0
- package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
- package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
- package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
- package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
- package/application/shells/charcell/components/PreviewPane/index.css +127 -0
- package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
- package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
- package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
- package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
- package/application/shells/charcell/components/Select/index.tsx +48 -0
- package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
- package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
- package/application/shells/charcell/components/StageBar/index.css +7 -0
- package/application/shells/charcell/components/StageBar/index.tsx +109 -0
- package/application/shells/charcell/components/StageSplit/index.css +11 -0
- package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
- package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
- package/application/shells/charcell/components/ViewControls/index.css +15 -0
- package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
- package/application/shells/charcell/components/ViewHeading/index.css +13 -0
- package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
- package/application/shells/charcell/components/ViewStage/index.css +9 -0
- package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
- package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
- package/application/shells/charcell/configuration.ts +45 -0
- package/application/shells/charcell/controller-context.ts +27 -0
- package/application/shells/charcell/fit-labels.ts +42 -0
- package/application/shells/charcell/html-warnings.ts +18 -0
- package/application/shells/charcell/index.tsx +82 -0
- package/application/shells/charcell/page-loading.html +57 -0
- package/application/shells/charcell/placement.ts +24 -0
- package/application/shells/charcell/shell-options.ts +23 -0
- package/application/shells/charcell/shell.css +37 -0
- package/application/shells/charcell/shell.tsx +25 -0
- package/application/shells/charcell/split-reset.ts +47 -0
- package/application/shells/charcell/stacked-rows.ts +117 -0
- package/application/shells/charcell/stage-toggles.ts +93 -0
- package/application/types/manifest.d.ts +6 -0
- package/application/types/preview-configuration.d.ts +10 -0
- package/application/types/preview-shell.d.ts +7 -0
- package/application/types/react-registry.d.ts +15 -0
- package/application/types/vue-registry.d.ts +6 -0
- package/dist/build.d.ts +24 -0
- package/dist/build.js +34 -0
- package/dist/check.d.ts +48 -0
- package/dist/check.js +781 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +88 -0
- package/dist/context.d.ts +179 -0
- package/dist/context.js +783 -0
- package/dist/controller/catalog.d.ts +339 -0
- package/dist/controller/catalog.js +367 -0
- package/dist/controller/create-preview-controller.d.ts +532 -0
- package/dist/controller/create-preview-controller.js +1707 -0
- package/dist/controller/editor-tree.d.ts +161 -0
- package/dist/controller/editor-tree.js +373 -0
- package/dist/controller/html-source.d.ts +62 -0
- package/dist/controller/html-source.js +61 -0
- package/dist/controller/index.d.ts +9 -0
- package/dist/controller/index.js +12 -0
- package/dist/controller/persistence.d.ts +191 -0
- package/dist/controller/persistence.js +315 -0
- package/dist/controller/regeneration.d.ts +30 -0
- package/dist/controller/regeneration.js +27 -0
- package/dist/controller/source.d.ts +80 -0
- package/dist/controller/source.js +236 -0
- package/dist/controller/store.d.ts +27 -0
- package/dist/controller/store.js +40 -0
- package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
- package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
- package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
- package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
- package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
- package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
- package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
- package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
- package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
- package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
- package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
- package/dist/declarations/src/controller/catalog.d.ts +339 -0
- package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
- package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
- package/dist/declarations/src/controller/html-source.d.ts +62 -0
- package/dist/declarations/src/controller/index.d.ts +9 -0
- package/dist/declarations/src/controller/persistence.d.ts +191 -0
- package/dist/declarations/src/controller/regeneration.d.ts +30 -0
- package/dist/declarations/src/controller/source.d.ts +80 -0
- package/dist/declarations/src/controller/store.d.ts +27 -0
- package/dist/declarations/src/emit/common.d.ts +103 -0
- package/dist/declarations/src/emit/manifest.d.ts +138 -0
- package/dist/declarations/src/emit/source.d.ts +87 -0
- package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
- package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
- package/dist/declarations/src/lib/naming.d.ts +29 -0
- package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
- package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
- package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
- package/dist/declarations/src/lib/types.d.ts +741 -0
- package/dist/declarations/src/options.d.ts +224 -0
- package/dist/declarations/src/protocol.d.ts +541 -0
- package/dist/declarations/src/react-view.d.ts +22 -0
- package/dist/declarations/src/react.d.ts +17 -0
- package/dist/declarations/src/shell.d.ts +99 -0
- package/dist/declarations/src/views.d.ts +152 -0
- package/dist/development-server.d.ts +121 -0
- package/dist/development-server.js +331 -0
- package/dist/emit/common.d.ts +103 -0
- package/dist/emit/common.js +170 -0
- package/dist/emit/html-source.d.ts +46 -0
- package/dist/emit/html-source.js +322 -0
- package/dist/emit/manifest.d.ts +138 -0
- package/dist/emit/manifest.js +74 -0
- package/dist/emit/react.d.ts +16 -0
- package/dist/emit/react.js +210 -0
- package/dist/emit/scratch.d.ts +40 -0
- package/dist/emit/scratch.js +116 -0
- package/dist/emit/source.d.ts +87 -0
- package/dist/emit/source.js +789 -0
- package/dist/emit/svelte.d.ts +9 -0
- package/dist/emit/svelte.js +177 -0
- package/dist/emit/vue.d.ts +2 -0
- package/dist/emit/vue.js +152 -0
- package/dist/enumerate.d.ts +2 -0
- package/dist/enumerate.js +162 -0
- package/dist/generate.d.ts +43 -0
- package/dist/generate.js +302 -0
- package/dist/html-stylesheet.d.ts +43 -0
- package/dist/html-stylesheet.js +145 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +21 -0
- package/dist/landing.d.ts +16 -0
- package/dist/landing.js +45 -0
- package/dist/lib/exported-types.d.ts +42 -0
- package/dist/lib/exported-types.js +86 -0
- package/dist/lib/html-attributes.d.ts +2 -0
- package/dist/lib/html-attributes.js +111 -0
- package/dist/lib/html-warnings.d.ts +19 -0
- package/dist/lib/html-warnings.js +79 -0
- package/dist/lib/jsx-roots.d.ts +22 -0
- package/dist/lib/jsx-roots.js +243 -0
- package/dist/lib/module-of-source.d.ts +18 -0
- package/dist/lib/module-of-source.js +24 -0
- package/dist/lib/naming.d.ts +29 -0
- package/dist/lib/naming.js +56 -0
- package/dist/lib/parse-type.d.ts +32 -0
- package/dist/lib/parse-type.js +103 -0
- package/dist/lib/react-spelling.d.ts +68 -0
- package/dist/lib/react-spelling.js +197 -0
- package/dist/lib/scenario-story.d.ts +41 -0
- package/dist/lib/scenario-story.js +84 -0
- package/dist/lib/slot-fill.d.ts +15 -0
- package/dist/lib/slot-fill.js +92 -0
- package/dist/lib/template-files.d.ts +14 -0
- package/dist/lib/template-files.js +25 -0
- package/dist/lib/types.d.ts +741 -0
- package/dist/lib/types.js +13 -0
- package/dist/lib/void-elements.d.ts +7 -0
- package/dist/lib/void-elements.js +13 -0
- package/dist/load.d.ts +37 -0
- package/dist/load.js +82 -0
- package/dist/model.d.ts +44 -0
- package/dist/model.js +464 -0
- package/dist/options.d.ts +224 -0
- package/dist/options.js +223 -0
- package/dist/prop-types.d.ts +26 -0
- package/dist/prop-types.js +102 -0
- package/dist/props-from.d.ts +84 -0
- package/dist/props-from.js +510 -0
- package/dist/protocol.d.ts +541 -0
- package/dist/protocol.js +819 -0
- package/dist/react-view.d.ts +22 -0
- package/dist/react-view.js +36 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +23 -0
- package/dist/sass-importers.d.ts +26 -0
- package/dist/sass-importers.js +32 -0
- package/dist/shell-acceptance.d.ts +21 -0
- package/dist/shell-acceptance.js +37 -0
- package/dist/shell-page.d.ts +36 -0
- package/dist/shell-page.js +90 -0
- package/dist/shell-peers.d.ts +38 -0
- package/dist/shell-peers.js +61 -0
- package/dist/shell.d.ts +99 -0
- package/dist/shell.js +52 -0
- package/dist/shells/charcell/page.d.ts +8 -0
- package/dist/shells/charcell/page.js +69 -0
- package/dist/story-source.d.ts +113 -0
- package/dist/story-source.js +271 -0
- package/dist/story.d.ts +5 -0
- package/dist/story.js +28 -0
- package/dist/testing.d.ts +218 -0
- package/dist/testing.js +284 -0
- package/dist/views.d.ts +152 -0
- package/dist/views.js +7 -0
- package/dist/vite-configuration.d.ts +114 -0
- package/dist/vite-configuration.js +578 -0
- package/package.json +123 -3
- package/src/build.ts +51 -0
- package/src/check.ts +1062 -0
- package/src/cli.ts +123 -0
- package/src/context.ts +1168 -0
- package/src/controller/catalog.ts +719 -0
- package/src/controller/create-preview-controller.ts +2683 -0
- package/src/controller/editor-tree.ts +530 -0
- package/src/controller/html-source.ts +114 -0
- package/src/controller/index.ts +105 -0
- package/src/controller/persistence.ts +403 -0
- package/src/controller/regeneration.ts +55 -0
- package/src/controller/source.ts +333 -0
- package/src/controller/store.ts +55 -0
- package/src/development-server.ts +442 -0
- package/src/emit/common.ts +201 -0
- package/src/emit/html-source.ts +402 -0
- package/src/emit/manifest.ts +241 -0
- package/src/emit/react.ts +263 -0
- package/src/emit/scratch.ts +175 -0
- package/src/emit/source.ts +1094 -0
- package/src/emit/svelte.ts +230 -0
- package/src/emit/vue.ts +194 -0
- package/src/enumerate.ts +220 -0
- package/src/generate.ts +443 -0
- package/src/html-stylesheet.ts +173 -0
- package/src/index.ts +60 -0
- package/src/landing.ts +58 -0
- package/src/lib/exported-types.ts +114 -0
- package/src/lib/html-attributes.ts +113 -0
- package/src/lib/html-warnings.ts +81 -0
- package/src/lib/jsx-roots.ts +238 -0
- package/src/lib/module-of-source.ts +26 -0
- package/src/lib/naming.ts +65 -0
- package/src/lib/parse-type.ts +143 -0
- package/src/lib/react-spelling.ts +235 -0
- package/src/lib/scenario-story.ts +97 -0
- package/src/lib/slot-fill.ts +102 -0
- package/src/lib/template-files.ts +31 -0
- package/src/lib/types.ts +778 -0
- package/src/lib/void-elements.ts +15 -0
- package/src/load.ts +144 -0
- package/src/model.ts +568 -0
- package/src/options.ts +435 -0
- package/src/prop-types.ts +133 -0
- package/src/props-from.ts +678 -0
- package/src/protocol.ts +1135 -0
- package/src/react-view.ts +45 -0
- package/src/react.ts +34 -0
- package/src/sass-importers.ts +45 -0
- package/src/shell-acceptance.ts +45 -0
- package/src/shell-page.ts +133 -0
- package/src/shell-peers.ts +70 -0
- package/src/shell.ts +194 -0
- package/src/shells/charcell/page.ts +82 -0
- package/src/story-source.ts +391 -0
- package/src/story.ts +42 -0
- package/src/testing.ts +497 -0
- package/src/views.ts +183 -0
- package/src/vite-configuration.ts +675 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// A hint beside a control, in place of a `title`: charcell's Tooltip, which
|
|
2
|
+
// opens as the reader hovers or focuses the control and closes on Escape. The
|
|
3
|
+
// control renders through `children`, handed the trigger's props to spread
|
|
4
|
+
// on the element the hint is for; with no text it renders bare. A control
|
|
5
|
+
// that is already another part's trigger (a popover's, a sortable grip) passes
|
|
6
|
+
// those `triggerProps`: the hint takes their id as its trigger's, so both
|
|
7
|
+
// find the one element, and the two descriptions stay. A hint the kit wrote
|
|
8
|
+
// (`markdown`) renders its markdown, as a description does.
|
|
9
|
+
//
|
|
10
|
+
// A control that takes its handlers on an element other than the one the
|
|
11
|
+
// reader sees passes `around`: the trigger is then a span around the control,
|
|
12
|
+
// which hears the pointer over any of it and the focus of anything inside,
|
|
13
|
+
// and the control is handed the description alone. charcell's Toggle is one:
|
|
14
|
+
// its handlers reach the checkbox it hides, which no pointer reaches. A
|
|
15
|
+
// disabled control takes no focus, so with `focusable` the span takes it in
|
|
16
|
+
// the control's place, and the hint opens from the keyboard too: a group
|
|
17
|
+
// named by the control's `label` (the hint's text without one) and described
|
|
18
|
+
// by the hint, so the stop says which control it stands for. Styled in its
|
|
19
|
+
// index.css.
|
|
20
|
+
import { Tooltip } from 'charcell/react/unlinked';
|
|
21
|
+
import { mergeProps } from '@zag-js/react';
|
|
22
|
+
import type { ReactNode } from 'react';
|
|
23
|
+
import { Markdown } from '../Markdown';
|
|
24
|
+
import './index.css';
|
|
25
|
+
|
|
26
|
+
export function Hint({
|
|
27
|
+
text,
|
|
28
|
+
markdown = false,
|
|
29
|
+
triggerProps,
|
|
30
|
+
around = false,
|
|
31
|
+
focusable = false,
|
|
32
|
+
label,
|
|
33
|
+
children,
|
|
34
|
+
}: {
|
|
35
|
+
text?: string;
|
|
36
|
+
markdown?: boolean;
|
|
37
|
+
triggerProps?: Record<string, unknown>;
|
|
38
|
+
around?: boolean;
|
|
39
|
+
focusable?: boolean;
|
|
40
|
+
label?: string;
|
|
41
|
+
children: (triggerProps: Record<string, unknown>) => ReactNode;
|
|
42
|
+
}) {
|
|
43
|
+
if (!text?.trim()) {
|
|
44
|
+
const bare = children(triggerProps ?? {});
|
|
45
|
+
return around ? <span className="Hint">{bare}</span> : <>{bare}</>;
|
|
46
|
+
}
|
|
47
|
+
const triggerId = typeof triggerProps?.id === 'string' ? triggerProps.id : undefined;
|
|
48
|
+
return (
|
|
49
|
+
<Tooltip
|
|
50
|
+
triggerId={around ? undefined : triggerId}
|
|
51
|
+
trigger={(tooltip) => {
|
|
52
|
+
if (!around)
|
|
53
|
+
return children(triggerProps ? merged(triggerProps, tooltip.triggerProps) : tooltip.triggerProps);
|
|
54
|
+
const { 'aria-describedby': described, ...wrapper } = tooltip.triggerProps;
|
|
55
|
+
const describedBy = typeof described === 'string' ? described : undefined;
|
|
56
|
+
return (
|
|
57
|
+
<span
|
|
58
|
+
{...wrapper}
|
|
59
|
+
className={`Hint${focusable ? ' Hint--focusable' : ''}`}
|
|
60
|
+
{...(focusable
|
|
61
|
+
? { tabIndex: 0, role: 'group', 'aria-label': label ?? text, 'aria-describedby': describedBy }
|
|
62
|
+
: {})}
|
|
63
|
+
>
|
|
64
|
+
{children(merged(triggerProps ?? {}, { 'aria-describedby': describedBy }))}
|
|
65
|
+
</span>
|
|
66
|
+
);
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{markdown ? <Markdown text={text} /> : text}
|
|
70
|
+
</Tooltip>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A part's trigger props with the hint's over them, keeping both descriptions. */
|
|
75
|
+
function merged(own: Record<string, unknown>, hint: Record<string, unknown>): Record<string, unknown> {
|
|
76
|
+
const describedBy = [own['aria-describedby'], hint['aria-describedby']].filter((id) => typeof id === 'string' && id);
|
|
77
|
+
return {
|
|
78
|
+
...mergeProps(own, hint),
|
|
79
|
+
...(describedBy.length ? { 'aria-describedby': describedBy.join(' ') } : { 'aria-describedby': undefined }),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// How many warnings a story's html has, as a dense charcell Button that opens
|
|
2
|
+
// them in a charcell Popover on a click, Enter or Space, where they stay open
|
|
3
|
+
// to be read and copied until a click outside or Escape closes them. Hovering
|
|
4
|
+
// or focusing it shows a short hint saying what it opens (Hint), as the
|
|
5
|
+
// description button's does. The pane that holds it gives its color through
|
|
6
|
+
// `className`.
|
|
7
|
+
import { Button, Popover } from 'charcell/react/unlinked';
|
|
8
|
+
import { Hint } from '../Hint';
|
|
9
|
+
import { Markdown } from '../Markdown';
|
|
10
|
+
import { htmlWarningsHint, htmlWarningsLabel } from '../../html-warnings';
|
|
11
|
+
|
|
12
|
+
export function HtmlWarnings({
|
|
13
|
+
warnings,
|
|
14
|
+
className,
|
|
15
|
+
attributes,
|
|
16
|
+
}: {
|
|
17
|
+
warnings: readonly string[];
|
|
18
|
+
className?: string;
|
|
19
|
+
attributes?: Record<string, string>;
|
|
20
|
+
}) {
|
|
21
|
+
return (
|
|
22
|
+
<Popover
|
|
23
|
+
label="html warnings"
|
|
24
|
+
width={64}
|
|
25
|
+
trigger={({ triggerProps }) => (
|
|
26
|
+
<Hint
|
|
27
|
+
text="what the html leaves out at rest"
|
|
28
|
+
triggerProps={triggerProps}
|
|
29
|
+
>
|
|
30
|
+
{(props) => (
|
|
31
|
+
<Button
|
|
32
|
+
variant="dense"
|
|
33
|
+
label={htmlWarningsLabel(warnings)}
|
|
34
|
+
className={className}
|
|
35
|
+
{...props}
|
|
36
|
+
{...attributes}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</Hint>
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
<Markdown text={htmlWarningsHint(warnings)} />
|
|
43
|
+
</Popover>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
@layer charcell.components {
|
|
2
|
+
/* The hint and the list's button, a band's items apart. */
|
|
3
|
+
.KeyHints {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 2ch;
|
|
7
|
+
}
|
|
8
|
+
.KeyHints__hint {
|
|
9
|
+
color: var(--charcell-mid);
|
|
10
|
+
}
|
|
11
|
+
/* The list: the keys in one column, what they do in the next. */
|
|
12
|
+
.KeyHints__list {
|
|
13
|
+
margin: 0;
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
16
|
+
column-gap: 2ch;
|
|
17
|
+
}
|
|
18
|
+
.KeyHints__entry {
|
|
19
|
+
display: contents;
|
|
20
|
+
}
|
|
21
|
+
.KeyHints__key {
|
|
22
|
+
font-weight: 700;
|
|
23
|
+
}
|
|
24
|
+
.KeyHints__meaning {
|
|
25
|
+
margin: 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// The shell's keys, and the hint of them at the end of the status bar:
|
|
2
|
+
// `/ find ? keys`, the second opening the list of them in a charcell Popover.
|
|
3
|
+
// A key acts while no field takes the typing - a text input, a select's
|
|
4
|
+
// list, the editor, the navigation's tree, whose rows a typed letter finds - and
|
|
5
|
+
// no pane holds the focus (a pane's keys are its story's); with Ctrl, Alt or
|
|
6
|
+
// Meta held it is the browser's.
|
|
7
|
+
//
|
|
8
|
+
// / find a component (the component combobox, its text selected)
|
|
9
|
+
// n show or hide the navigation
|
|
10
|
+
// s o c e show or hide the scenario, output, code and events views
|
|
11
|
+
// 1-n show or hide the targets, in the top bar's order (the kit's
|
|
12
|
+
// targets numbered, 1-3 for three)
|
|
13
|
+
// w wrap the code panes' lines, or not
|
|
14
|
+
// ? the list of keys
|
|
15
|
+
//
|
|
16
|
+
// The list also names Enter on a focused split handle, which puts the split
|
|
17
|
+
// back where it started (split-reset.ts, as a double-click on it does); the
|
|
18
|
+
// handle takes it, not this listener.
|
|
19
|
+
//
|
|
20
|
+
// Views and targets follow the narrow layout's one-at-a-time rule
|
|
21
|
+
// (stage-toggles.ts). Styled in its index.css.
|
|
22
|
+
import { Button, Popover } from 'charcell/react/unlinked';
|
|
23
|
+
import { Hint } from '../Hint';
|
|
24
|
+
import { useEffect, useState } from 'react';
|
|
25
|
+
import { usePreviewController } from '../../controller-context';
|
|
26
|
+
import { NAVIGATION_OPTION, WRAP_OPTION } from '../../shell-options';
|
|
27
|
+
import { toggleTarget, toggleView, useNarrow, VIEW_OPTIONS } from '../../stage-toggles';
|
|
28
|
+
import './index.css';
|
|
29
|
+
|
|
30
|
+
/** The keys, as the list shows them. */
|
|
31
|
+
function keys(targets: number): [string, string][] {
|
|
32
|
+
return [
|
|
33
|
+
['/', 'find a component'],
|
|
34
|
+
['n', 'show or hide the navigation'],
|
|
35
|
+
['s o c e', 'show or hide the scenario, output, code and events views'],
|
|
36
|
+
[targets > 1 ? `1-${targets}` : '1', targets > 1 ? 'show or hide the targets' : 'show or hide the target'],
|
|
37
|
+
['w', 'wrap the code panes’ lines, or not'],
|
|
38
|
+
['enter', 'on a split handle, put the split back where it started (or double-click it)'],
|
|
39
|
+
['?', 'this list'],
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Whether a key pressed with focus on `target` is the field's to take. */
|
|
44
|
+
function typing(target: EventTarget | null): boolean {
|
|
45
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
46
|
+
if (target.isContentEditable) return true;
|
|
47
|
+
if (target.closest('input, textarea, select, [role="listbox"], [role="combobox"], [role="dialog"], [role="tree"]')) return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function KeyHints() {
|
|
52
|
+
const controller = usePreviewController();
|
|
53
|
+
const narrow = useNarrow();
|
|
54
|
+
const [open, setOpen] = useState(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
function onKey(event: KeyboardEvent) {
|
|
57
|
+
if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) return;
|
|
58
|
+
if (typing(event.target)) return;
|
|
59
|
+
const state = controller.getState();
|
|
60
|
+
const viewKey = { s: 0, o: 1, c: 2, e: 3 }[event.key];
|
|
61
|
+
if (event.key === '/') {
|
|
62
|
+
// The component combobox, its text selected: typing replaces the
|
|
63
|
+
// current name and opens the list, narrowed to the matches.
|
|
64
|
+
const find = document.querySelector<HTMLInputElement>('[data-testid="select-component"]');
|
|
65
|
+
if (!find) return;
|
|
66
|
+
find.focus();
|
|
67
|
+
find.select();
|
|
68
|
+
} else if (event.key === '?') setOpen((shown) => !shown);
|
|
69
|
+
else if (viewKey !== undefined) toggleView(controller, VIEW_OPTIONS[viewKey], narrow);
|
|
70
|
+
else if (/^[1-9]$/.test(event.key)) {
|
|
71
|
+
const target = controller.catalog.targets[Number(event.key) - 1];
|
|
72
|
+
if (!target) return;
|
|
73
|
+
toggleTarget(controller, target, narrow);
|
|
74
|
+
} else if (event.key === 'n') controller.actions.setOption(NAVIGATION_OPTION, state.options[NAVIGATION_OPTION] === false);
|
|
75
|
+
else if (event.key === 'w') controller.actions.setOption(WRAP_OPTION, state.options[WRAP_OPTION] === false);
|
|
76
|
+
else return;
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
}
|
|
79
|
+
window.addEventListener('keydown', onKey);
|
|
80
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
81
|
+
}, [controller, narrow]);
|
|
82
|
+
return (
|
|
83
|
+
<span className="KeyHints">
|
|
84
|
+
<span className="KeyHints__hint">/ find</span>
|
|
85
|
+
<Popover
|
|
86
|
+
label="keys"
|
|
87
|
+
open={open}
|
|
88
|
+
onOpenChange={setOpen}
|
|
89
|
+
trigger={({ triggerProps }) => (
|
|
90
|
+
<Hint text="the shell's keys" triggerProps={triggerProps}>
|
|
91
|
+
{(props) => <Button variant="dense" label="? keys" {...props} />}
|
|
92
|
+
</Hint>
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
<dl className="KeyHints__list">
|
|
96
|
+
{keys(controller.catalog.targets.length).map(([key, meaning]) => (
|
|
97
|
+
<div key={key} className="KeyHints__entry">
|
|
98
|
+
<dt className="KeyHints__key">{key}</dt>
|
|
99
|
+
<dd className="KeyHints__meaning">{meaning}</dd>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</dl>
|
|
103
|
+
</Popover>
|
|
104
|
+
</span>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
@layer charcell.components {
|
|
2
|
+
/* The block's place: the rest of the code pane, its iframe filling it. */
|
|
3
|
+
.KitCodeBlock {
|
|
4
|
+
position: relative;
|
|
5
|
+
flex: 1 1 auto;
|
|
6
|
+
display: flex;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
}
|
|
9
|
+
/* While the block's iframe loads, as a preview pane's does (PreviewPane). */
|
|
10
|
+
.KitCodeBlock:has(> iframe:not([data-preview-shown]))::after {
|
|
11
|
+
content: 'loading';
|
|
12
|
+
position: absolute;
|
|
13
|
+
inset: 0;
|
|
14
|
+
display: grid;
|
|
15
|
+
place-items: center;
|
|
16
|
+
color: var(--charcell-mid);
|
|
17
|
+
visibility: hidden;
|
|
18
|
+
animation:
|
|
19
|
+
KitCodeBlock-loading 0s 300ms forwards,
|
|
20
|
+
KitCodeBlock-dots 1.2s 300ms steps(1, end) infinite;
|
|
21
|
+
}
|
|
22
|
+
@keyframes KitCodeBlock-loading {
|
|
23
|
+
to {
|
|
24
|
+
visibility: visible;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/* The dots count up, one a step; spaces hold the dots' place, so the
|
|
28
|
+
word stays put in the monospace face. */
|
|
29
|
+
@keyframes KitCodeBlock-dots {
|
|
30
|
+
0% {
|
|
31
|
+
content: 'loading\00a0\00a0\00a0';
|
|
32
|
+
}
|
|
33
|
+
25% {
|
|
34
|
+
content: 'loading.\00a0\00a0';
|
|
35
|
+
}
|
|
36
|
+
50% {
|
|
37
|
+
content: 'loading..\00a0';
|
|
38
|
+
}
|
|
39
|
+
75%,
|
|
40
|
+
100% {
|
|
41
|
+
content: 'loading...';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
@media (prefers-reduced-motion: reduce) {
|
|
45
|
+
.KitCodeBlock:has(> iframe:not([data-preview-shown]))::after {
|
|
46
|
+
animation: KitCodeBlock-loading 0s 300ms forwards;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
.KitCodeBlock > iframe {
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-height: 0;
|
|
52
|
+
width: 100%;
|
|
53
|
+
border: 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The kit's own code block (`catalog.codeBlock`) in a code pane: the element
|
|
2
|
+
// the controller mounts the block's iframe in, filling the pane's place for
|
|
3
|
+
// the source. The block shows the target's source, follows it, and wraps
|
|
4
|
+
// its long lines as the shell's `wrap` option says. Styled in its index.css.
|
|
5
|
+
import { useEffect, useRef } from 'react';
|
|
6
|
+
import type { PreviewTarget } from '../../../../../src/shell';
|
|
7
|
+
import { usePreviewController, usePreviewState } from '../../controller-context';
|
|
8
|
+
import { WRAP_OPTION } from '../../shell-options';
|
|
9
|
+
import './index.css';
|
|
10
|
+
|
|
11
|
+
export function KitCodeBlock({ target }: { target: PreviewTarget }) {
|
|
12
|
+
const controller = usePreviewController();
|
|
13
|
+
const element = useRef<HTMLDivElement>(null);
|
|
14
|
+
const wrapped = usePreviewState((state) => state.options[WRAP_OPTION] !== false);
|
|
15
|
+
useEffect(() => controller.frames.mountCode(element.current!, target), [controller, target]);
|
|
16
|
+
useEffect(() => controller.frames.wrapCode(wrapped), [controller, wrapped]);
|
|
17
|
+
return <div ref={element} className="KitCodeBlock" data-preview-kit-code={target} />;
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// A description as the kit wrote it, in markdown: parsed into React elements
|
|
2
|
+
// and set in charcell's RichText, which gives its paragraphs, inline code,
|
|
3
|
+
// code blocks, lists and links charcell's cells. Raw html in the text is shown
|
|
4
|
+
// as text, never parsed, so a tag a description mentions without backticks
|
|
5
|
+
// cannot swallow what follows it. A code block scrolls rather than wraps.
|
|
6
|
+
import { compiler } from 'markdown-to-jsx/react';
|
|
7
|
+
import { RichText } from 'charcell/react/unlinked';
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
export function Markdown({ text }: { text: string }) {
|
|
11
|
+
const content = useMemo(
|
|
12
|
+
() => compiler(text.trim(), { disableParsingRawHTML: true, forceBlock: true, wrapper: null }),
|
|
13
|
+
[text],
|
|
14
|
+
);
|
|
15
|
+
return <RichText>{content}</RichText>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer charcell.components {
|
|
2
|
+
/* The navigation: fills its panel of the navigation split, its
|
|
3
|
+
charcell ScrollArea filling it in turn, and ends a row past the tree. */
|
|
4
|
+
.Navigation {
|
|
5
|
+
flex: 1 1 auto;
|
|
6
|
+
display: flex;
|
|
7
|
+
min-width: 0;
|
|
8
|
+
min-height: 0;
|
|
9
|
+
}
|
|
10
|
+
.Navigation__content {
|
|
11
|
+
padding-bottom: var(--charcell-row);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// The navigation renders the catalog's navigation tree: per source, custom views (a
|
|
2
|
+
// tokens page, an icon gallery) and the component tree interleave by order, the
|
|
3
|
+
// root (core) source hoisted to the top level and every other source a collapsible
|
|
4
|
+
// wrapper. The tree is the controller's catalog data; this component only draws it.
|
|
5
|
+
// It scrolls in a charcell ScrollArea, a whole row at a time, its scrollbar
|
|
6
|
+
// drawn in cells; a scroll at either end stops there, moving nothing else.
|
|
7
|
+
// Which groups are collapsed is its parent's to keep (`collapsed`), so they
|
|
8
|
+
// stay as they were left while the navigation is hidden and shown.
|
|
9
|
+
import { useEffect, useRef, type Dispatch, type SetStateAction } from 'react';
|
|
10
|
+
import { ScrollArea, Tree, TreeGroup, TreeItem } from 'charcell/react/unlinked';
|
|
11
|
+
import type { PreviewNavigationNode } from '../../../../../src/shell';
|
|
12
|
+
import { usePreviewController, usePreviewState } from '../../controller-context';
|
|
13
|
+
import './index.css';
|
|
14
|
+
|
|
15
|
+
export function Navigation({
|
|
16
|
+
collapsed,
|
|
17
|
+
onCollapsedChange,
|
|
18
|
+
}: {
|
|
19
|
+
/** The keys of the collapsed groups. */
|
|
20
|
+
collapsed: ReadonlySet<string>;
|
|
21
|
+
/** Sets the keys as a group opens or collapses, as a state setter does. */
|
|
22
|
+
onCollapsedChange: Dispatch<SetStateAction<ReadonlySet<string>>>;
|
|
23
|
+
}) {
|
|
24
|
+
const controller = usePreviewController();
|
|
25
|
+
const selection = usePreviewState((state) => state.selection);
|
|
26
|
+
// A page opened on a selection (a followed link) scrolls the selected entry
|
|
27
|
+
// to the middle of the navigation once, so a long list opens around it,
|
|
28
|
+
// with what comes before and after it in view.
|
|
29
|
+
const navigation = useRef<HTMLElement>(null);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
// The navigation's scrolling region (its ScrollArea's, by its role)
|
|
32
|
+
// scrolls, a whole row at a time, and nothing around it.
|
|
33
|
+
const selected = navigation.current?.querySelector('[aria-current]');
|
|
34
|
+
const viewport = selected?.closest('[role="region"]');
|
|
35
|
+
if (!(selected instanceof HTMLElement) || !(viewport instanceof HTMLElement)) return;
|
|
36
|
+
const row = parseFloat(getComputedStyle(viewport).lineHeight) || 1;
|
|
37
|
+
const offset =
|
|
38
|
+
selected.getBoundingClientRect().top - viewport.getBoundingClientRect().top + viewport.scrollTop;
|
|
39
|
+
viewport.scrollTop = Math.max(0, Math.round((offset - viewport.clientHeight / 2) / row) * row);
|
|
40
|
+
}, []);
|
|
41
|
+
function syncGroup(key: string, open: boolean) {
|
|
42
|
+
onCollapsedChange((previous) => {
|
|
43
|
+
const next = new Set(previous);
|
|
44
|
+
if (open) next.delete(key);
|
|
45
|
+
else next.add(key);
|
|
46
|
+
return next;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// The nested navigation: groups are charcell TreeGroups (open state on the
|
|
50
|
+
// row's aria-expanded, connectors structural), leaves TreeItems; depth 0 renders the
|
|
51
|
+
// bold root variants. The Tree is one tab stop, its rows reached with the
|
|
52
|
+
// arrow keys.
|
|
53
|
+
function renderNavigation(nodes: PreviewNavigationNode[], depth: number): React.ReactNode {
|
|
54
|
+
return nodes.map((node) => {
|
|
55
|
+
if (node.kind === 'group') {
|
|
56
|
+
return (
|
|
57
|
+
<TreeGroup
|
|
58
|
+
key={node.key}
|
|
59
|
+
root={depth === 0}
|
|
60
|
+
label={node.label}
|
|
61
|
+
open={!collapsed.has(node.key)}
|
|
62
|
+
onOpenChange={(open) => syncGroup(node.key, open)}
|
|
63
|
+
>
|
|
64
|
+
{renderNavigation(node.children, depth + 1)}
|
|
65
|
+
</TreeGroup>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const selected = node.id === selection.id && (node.kind === 'view') === (selection.kind === 'view');
|
|
69
|
+
return (
|
|
70
|
+
<TreeItem
|
|
71
|
+
key={node.kind === 'view' ? `view:${node.id}` : node.id}
|
|
72
|
+
data-preview-entry={`${node.kind}:${node.id}`}
|
|
73
|
+
root={depth === 0}
|
|
74
|
+
label={node.label}
|
|
75
|
+
current={selected}
|
|
76
|
+
// The selected entry stays focusable, the tree's one tab stop;
|
|
77
|
+
// choosing it again changes nothing.
|
|
78
|
+
onClick={() => {
|
|
79
|
+
if (!selected) controller.actions.select({ kind: node.kind, id: node.id });
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<nav ref={navigation} className="Navigation">
|
|
88
|
+
{/* The composed navigation: depth-0 entries are bold roots (a source wrapper, the
|
|
89
|
+
"Components" tree, or a root-level view such as a tokens page); deeper entries
|
|
90
|
+
are the box-drawing category tree. Groups collapse; leaves select. */}
|
|
91
|
+
<ScrollArea label="components" overscroll="none" fill>
|
|
92
|
+
<div className="Navigation__content">
|
|
93
|
+
<Tree aria-label="stories and views">{renderNavigation(controller.catalog.navigation, 0)}</Tree>
|
|
94
|
+
</div>
|
|
95
|
+
</ScrollArea>
|
|
96
|
+
</nav>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@layer charcell.components {
|
|
2
|
+
/* The body under the prompt bar, which the split fills. */
|
|
3
|
+
.NavigationSplit {
|
|
4
|
+
display: grid;
|
|
5
|
+
grid-template: minmax(0, 1fr) / minmax(0, 1fr);
|
|
6
|
+
min-height: 0;
|
|
7
|
+
}
|
|
8
|
+
/* The navigation's panel: the navigation, then the navigation's line, a column
|
|
9
|
+
down the panel's height. */
|
|
10
|
+
.NavigationSplit__navigation {
|
|
11
|
+
flex: 1 1 auto;
|
|
12
|
+
display: flex;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The body under the prompt bar: the navigation beside the stage, in a charcell
|
|
2
|
+
// Splitter. The navigation ends in its line, a faint vertical Separator joined to
|
|
3
|
+
// the prompt bar's and the status bar's rules above and below as ┬ and ┴; the
|
|
4
|
+
// handle is the empty column between that line and the stage's, its grip in
|
|
5
|
+
// the middle, and drags the boundary a whole column at a time. The navigation
|
|
6
|
+
// starts wide enough for its longest entry, as the tree indents it, from 27
|
|
7
|
+
// columns up to 40, its line one more, measured by the split in the face's
|
|
8
|
+
// own columns once the fonts have loaded; keeps at least 16, so
|
|
9
|
+
// the tree's labels still read; and keeps its columns when the window
|
|
10
|
+
// resizes, the stage taking the difference. While the navigation is hidden the
|
|
11
|
+
// split holds the stage alone, in the same place, so the stage keeps its
|
|
12
|
+
// frames.
|
|
13
|
+
import { useMemo, type ReactNode } from 'react';
|
|
14
|
+
import type { PreviewNavigationNode } from '../../../../../src/shell';
|
|
15
|
+
import { usePreviewController } from '../../controller-context';
|
|
16
|
+
import { Separator, SplitterDriver, SplitterHandle, SplitterPanel, SplitterVisual } from 'charcell/react/unlinked';
|
|
17
|
+
import './index.css';
|
|
18
|
+
|
|
19
|
+
/** The fewest and the most columns the navigation starts at, its line one more. */
|
|
20
|
+
const NAVIGATION_START_COLUMNS = { least: 27, most: 40 };
|
|
21
|
+
|
|
22
|
+
/** The columns the navigation's entries take as the tree lays them out: a
|
|
23
|
+
* cell's margin, three columns of tree lines per level, the label, on a
|
|
24
|
+
* group a cell and its `[+]` after it; then a cell's margin and the
|
|
25
|
+
* scrollbar's column. */
|
|
26
|
+
function navigationColumns(nodes: PreviewNavigationNode[], depth = 0): number {
|
|
27
|
+
return nodes.reduce((widest, node) => {
|
|
28
|
+
const disclosure = node.kind === 'group' ? 4 : 0;
|
|
29
|
+
const own = 1 + depth * 3 + disclosure + node.label.length + 2;
|
|
30
|
+
const children = node.kind === 'group' ? navigationColumns(node.children, depth + 1) : 0;
|
|
31
|
+
return Math.max(widest, own, children);
|
|
32
|
+
}, 0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The fewest columns the navigation keeps, its line one more. */
|
|
36
|
+
const NAVIGATION_LEAST_COLUMNS = 16;
|
|
37
|
+
|
|
38
|
+
export function NavigationSplit({ navigation, main, shown }: { navigation: ReactNode; main: ReactNode; shown: boolean }) {
|
|
39
|
+
const { navigation: entries } = usePreviewController().catalog;
|
|
40
|
+
const startColumns = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
Math.min(NAVIGATION_START_COLUMNS.most, Math.max(NAVIGATION_START_COLUMNS.least, navigationColumns(entries))),
|
|
43
|
+
[entries],
|
|
44
|
+
);
|
|
45
|
+
const panels = useMemo(
|
|
46
|
+
() =>
|
|
47
|
+
shown
|
|
48
|
+
? [
|
|
49
|
+
{
|
|
50
|
+
id: 'navigation',
|
|
51
|
+
minimumCells: NAVIGATION_LEAST_COLUMNS + 1,
|
|
52
|
+
keepCells: true,
|
|
53
|
+
defaultCells: startColumns + 1,
|
|
54
|
+
},
|
|
55
|
+
{ id: 'main' },
|
|
56
|
+
]
|
|
57
|
+
: [{ id: 'main' }],
|
|
58
|
+
[shown, startColumns],
|
|
59
|
+
);
|
|
60
|
+
return (
|
|
61
|
+
<div className="NavigationSplit">
|
|
62
|
+
<SplitterDriver panels={panels}>
|
|
63
|
+
{({ api }) => (
|
|
64
|
+
<SplitterVisual fill {...api.getRootProps()}>
|
|
65
|
+
{/* The navigation and its handle come first and leave holes while
|
|
66
|
+
hidden, so the stage keeps its place in the tree. */}
|
|
67
|
+
{shown && (
|
|
68
|
+
<SplitterPanel {...api.getPanelProps({ id: 'navigation' })}>
|
|
69
|
+
<div className="NavigationSplit__navigation">
|
|
70
|
+
{navigation}
|
|
71
|
+
<Separator orientation="vertical" decorative join="both" />
|
|
72
|
+
</div>
|
|
73
|
+
</SplitterPanel>
|
|
74
|
+
)}
|
|
75
|
+
{shown && <SplitterHandle label="resize navigation" {...api.getResizeTriggerProps({ id: 'navigation:main' })} />}
|
|
76
|
+
<SplitterPanel {...api.getPanelProps({ id: 'main' })}>{main}</SplitterPanel>
|
|
77
|
+
</SplitterVisual>
|
|
78
|
+
)}
|
|
79
|
+
</SplitterDriver>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// The preview's option controls, rendered from the descriptors the controller
|
|
2
|
+
// holds: the kit's options and the shell's own. An option says what it means,
|
|
3
|
+
// and this shell decides where it goes (placement.ts). The widget follows the
|
|
4
|
+
// option's kind: a select for a choice, an on/off toggle for a boolean.
|
|
5
|
+
import { Field as CharcellField, Toggle } from 'charcell/react/unlinked';
|
|
6
|
+
import { useId } from 'react';
|
|
7
|
+
import { optionKind, type OptionValue, type PreviewOptionDescriptor } from '../../../../../src/shell';
|
|
8
|
+
import type { CharcellRegion } from '../../configuration';
|
|
9
|
+
import { usePreviewController, usePreviewState, useShellConfiguration } from '../../controller-context';
|
|
10
|
+
import { optionPlacement } from '../../placement';
|
|
11
|
+
import { Select } from '../Select';
|
|
12
|
+
import { Hint } from '../Hint';
|
|
13
|
+
|
|
14
|
+
// One preview-option control (a Select or an on/off toggle).
|
|
15
|
+
function OptionControl({
|
|
16
|
+
option,
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
}: {
|
|
20
|
+
option: PreviewOptionDescriptor;
|
|
21
|
+
value: OptionValue | undefined;
|
|
22
|
+
onChange: (value: OptionValue) => void;
|
|
23
|
+
}) {
|
|
24
|
+
const labelId = useId();
|
|
25
|
+
if (optionKind(option) === 'boolean') {
|
|
26
|
+
// The toggle's text is its state; the option's label names it.
|
|
27
|
+
return (
|
|
28
|
+
<CharcellField label={option.label} labelId={labelId}>
|
|
29
|
+
<Hint text={option.title} markdown around>
|
|
30
|
+
{(hint) => (
|
|
31
|
+
<Toggle
|
|
32
|
+
variant="dense"
|
|
33
|
+
{...hint}
|
|
34
|
+
aria-labelledby={labelId}
|
|
35
|
+
label={value ? 'on' : 'off'}
|
|
36
|
+
pressed={Boolean(value)}
|
|
37
|
+
onPressedChange={() => onChange(!value)}
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
</Hint>
|
|
41
|
+
</CharcellField>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return (
|
|
45
|
+
<Select
|
|
46
|
+
label={option.label}
|
|
47
|
+
value={String(value ?? '')}
|
|
48
|
+
onChange={(value) => onChange(value)}
|
|
49
|
+
options={option.values ?? []}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The controls of the kit's options placed in one region, in registry order.
|
|
55
|
+
// The options the current story marks unavailable are hidden.
|
|
56
|
+
export function OptionControls({ placement }: { placement: CharcellRegion }) {
|
|
57
|
+
const controller = usePreviewController();
|
|
58
|
+
const { groups } = useShellConfiguration();
|
|
59
|
+
const values = usePreviewState((state) => state.options);
|
|
60
|
+
const unavailable = usePreviewState((state) => state.unavailableOptions);
|
|
61
|
+
const options = kitOptions(controller.options).filter(
|
|
62
|
+
(option) => optionPlacement(option, groups) === placement && !unavailable.includes(option.id),
|
|
63
|
+
);
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
{options.map((option) => (
|
|
67
|
+
<OptionControl
|
|
68
|
+
key={option.id}
|
|
69
|
+
option={option}
|
|
70
|
+
value={values[option.id]}
|
|
71
|
+
onChange={(value) => controller.actions.setOption(option.id, value)}
|
|
72
|
+
/>
|
|
73
|
+
))}
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Whether any option the current story offers is placed in a region - so a
|
|
79
|
+
* bar can leave out an empty zone. */
|
|
80
|
+
export function useHasOptionControls(placement: CharcellRegion): boolean {
|
|
81
|
+
const controller = usePreviewController();
|
|
82
|
+
const { groups } = useShellConfiguration();
|
|
83
|
+
const unavailable = usePreviewState((state) => state.unavailableOptions);
|
|
84
|
+
return kitOptions(controller.options).some(
|
|
85
|
+
(option) => optionPlacement(option, groups) === placement && !unavailable.includes(option.id),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The kit's options: the shell's own (the navigation, the views, the code
|
|
90
|
+
* panes' wrapping) have controls of their own. */
|
|
91
|
+
function kitOptions(options: PreviewOptionDescriptor[]): PreviewOptionDescriptor[] {
|
|
92
|
+
return options.filter((option) => option.origin === 'kit');
|
|
93
|
+
}
|