@charcuterie/ui 3.4.0 → 3.5.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.
Files changed (63) hide show
  1. package/README.md +8 -0
  2. package/dist/markdownEditorCodeMirror/livePreview.d.ts.map +1 -1
  3. package/dist/markdownEditorCodeMirror/livePreview.js +346 -3
  4. package/dist/markdownEditorCodeMirror/livePreview.js.map +1 -1
  5. package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts +99 -1
  6. package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts.map +1 -1
  7. package/dist/markdownEditorCodeMirror/livePreviewRanges.js +370 -19
  8. package/dist/markdownEditorCodeMirror/livePreviewRanges.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/Accordion/Accordion.stories.tsx +1 -1
  11. package/src/AdaptiveGrid/AdaptiveGrid.stories.tsx +1 -1
  12. package/src/Alert/Alert.stories.tsx +1 -1
  13. package/src/Badge/Badge.stories.tsx +1 -1
  14. package/src/Board/Board.stories.tsx +1 -1
  15. package/src/Button/Button.stories.tsx +1 -1
  16. package/src/ButtonLink/ButtonLink.stories.tsx +1 -1
  17. package/src/Card/Card.stories.tsx +1 -1
  18. package/src/Checkbox/Checkbox.stories.tsx +1 -1
  19. package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.stories.tsx +1 -1
  20. package/src/ColorSchemeToggle/ColorSchemeToggle.stories.tsx +1 -1
  21. package/src/Combobox/Combobox.stories.tsx +1 -1
  22. package/src/DataTable/DataTable.stories.tsx +1 -1
  23. package/src/DatePicker/DatePicker.stories.tsx +1 -1
  24. package/src/Dialog/Dialog.stories.tsx +1 -1
  25. package/src/EmptyState/EmptyState.stories.tsx +1 -1
  26. package/src/Field/Field.stories.tsx +1 -1
  27. package/src/FileDropZone/FileDropZone.stories.tsx +1 -1
  28. package/src/Header/Header.stories.tsx +1 -1
  29. package/src/IconButton/IconButton.stories.tsx +1 -1
  30. package/src/Lightbox/Lightbox.stories.tsx +1 -1
  31. package/src/Listbox/Listbox.stories.tsx +1 -1
  32. package/src/LiveStatusIndicator/LiveStatusIndicator.stories.tsx +1 -1
  33. package/src/LogViewer/LogViewer.stories.tsx +1 -1
  34. package/src/Main/Main.stories.tsx +1 -1
  35. package/src/MarkdownEditor/MarkdownEditor.stories.tsx +1 -1
  36. package/src/MediaTile/MediaTile.stories.tsx +1 -1
  37. package/src/Menu/Menu.stories.tsx +1 -1
  38. package/src/Modal/Modal.stories.tsx +1 -1
  39. package/src/Picker/Picker.stories.tsx +1 -1
  40. package/src/Popover/Popover.stories.tsx +1 -1
  41. package/src/ProgressBar/ProgressBar.stories.tsx +1 -1
  42. package/src/QueryBuilder/QueryBuilder.stories.tsx +1 -1
  43. package/src/RadioGroup/RadioGroup.stories.tsx +1 -1
  44. package/src/Rail/Rail.stories.tsx +1 -1
  45. package/src/SegmentedControl/SegmentedControl.stories.tsx +1 -1
  46. package/src/Select/Select.stories.tsx +17 -6
  47. package/src/Shell/Shell.stories.tsx +1 -1
  48. package/src/Skeleton/Skeleton.stories.tsx +1 -1
  49. package/src/SortableTableHeader/SortableTableHeader.stories.tsx +1 -1
  50. package/src/Spinner/Spinner.stories.tsx +1 -1
  51. package/src/Swatch/Swatch.stories.tsx +1 -1
  52. package/src/Switch/Switch.stories.tsx +1 -1
  53. package/src/Tabs/Tabs.stories.tsx +1 -1
  54. package/src/TextLink/TextLink.stories.tsx +1 -1
  55. package/src/Toast/Toast.stories.tsx +1 -1
  56. package/src/Toolbar/Toolbar.stories.tsx +1 -1
  57. package/src/Tooltip/Tooltip.stories.tsx +1 -1
  58. package/src/VisuallyHidden/VisuallyHidden.stories.tsx +1 -1
  59. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.mdx +28 -0
  60. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.stories.tsx +43 -1
  61. package/src/markdownEditorCodeMirror/livePreview.ts +465 -3
  62. package/src/markdownEditorCodeMirror/livePreviewRanges.test.ts +349 -8
  63. package/src/markdownEditorCodeMirror/livePreviewRanges.ts +669 -27
@@ -10,7 +10,7 @@ import { FileDropZone } from "./FileDropZone.tsx"
10
10
  const noop = () => undefined
11
11
 
12
12
  const meta = {
13
- title: "Components/FileDropZone",
13
+ title: "Components/Controls/FileDropZone",
14
14
  component: FileDropZone,
15
15
  parameters: { layout: "padded" },
16
16
  args: {
@@ -16,7 +16,7 @@ import {
16
16
  import { Header } from "./Header.tsx"
17
17
 
18
18
  const meta = {
19
- title: "Components/Header",
19
+ title: "Components/Layout/Header",
20
20
  component: Header,
21
21
  parameters: { layout: "fullscreen" },
22
22
  argTypes: {
@@ -27,7 +27,7 @@ import { IconButton } from "./IconButton.tsx"
27
27
  const UNDO_ICON = <UndoIcon />
28
28
 
29
29
  const meta = {
30
- title: "Components/IconButton",
30
+ title: "Components/Actions/IconButton",
31
31
  component: IconButton,
32
32
  parameters: { layout: "padded" },
33
33
  argTypes: { size: controlSizeArgType },
@@ -33,7 +33,7 @@ const PosterThumb = (): ReactNode => (
33
33
  )
34
34
 
35
35
  const meta = {
36
- title: "Components/Lightbox",
36
+ title: "Components/Overlays/Lightbox",
37
37
  component: Lightbox,
38
38
  parameters: { layout: "padded" },
39
39
  args: {
@@ -70,7 +70,7 @@ const TRACKS: ListboxItem[] = [
70
70
  ]
71
71
 
72
72
  const meta = {
73
- title: "Components/Listbox",
73
+ title: "Components/Controls/Listbox",
74
74
  component: Listbox,
75
75
  parameters: { layout: "padded" },
76
76
  argTypes: { placement: placementArgType },
@@ -24,7 +24,7 @@ const CONNECTION_STATUSES = [
24
24
  ] as const
25
25
 
26
26
  const meta = {
27
- title: "Components/LiveStatusIndicator",
27
+ title: "Components/Feedback/LiveStatusIndicator",
28
28
  component: LiveStatusIndicator,
29
29
  parameters: { layout: "padded" },
30
30
  argTypes: { status: connectionStatusArgType },
@@ -46,7 +46,7 @@ const LONG_LINES: LogLine[] = Array.from(
46
46
  )
47
47
 
48
48
  const meta = {
49
- title: "Components/LogViewer",
49
+ title: "Components/Data/LogViewer",
50
50
  component: LogViewer,
51
51
  parameters: { layout: "padded" },
52
52
  args: {
@@ -24,7 +24,7 @@ const CONTENT_WIDTH_OPTIONS = [
24
24
  ] as const
25
25
 
26
26
  const meta = {
27
- title: "Components/Main",
27
+ title: "Components/Layout/Main",
28
28
  component: Main,
29
29
  parameters: { layout: "fullscreen" },
30
30
  argTypes: {
@@ -45,7 +45,7 @@ ip -br addr show
45
45
  const noop = () => undefined
46
46
 
47
47
  const meta = {
48
- title: "Components/MarkdownEditor",
48
+ title: "Components/Controls/MarkdownEditor",
49
49
  component: MarkdownEditor,
50
50
  parameters: { layout: "padded" },
51
51
  args: {
@@ -25,7 +25,7 @@ const toPosterSrc = (label: string) =>
25
25
  const MISSING_SRC = "/charcuterie-missing-poster.png"
26
26
 
27
27
  const meta = {
28
- title: "Components/MediaTile",
28
+ title: "Components/Data/MediaTile",
29
29
  component: MediaTile,
30
30
  parameters: { layout: "padded" },
31
31
  args: { aspectRatio: "poster" },
@@ -141,7 +141,7 @@ const TooltipTriggerHarness = ({
141
141
  }
142
142
 
143
143
  const meta = {
144
- title: "Components/Menu",
144
+ title: "Components/Actions/Menu",
145
145
  component: Menu,
146
146
  parameters: { layout: "centered" },
147
147
  argTypes: { placement: placementArgType },
@@ -6,7 +6,7 @@ import { Button } from "../Button/Button.tsx"
6
6
  import { Modal } from "./Modal.tsx"
7
7
 
8
8
  const meta = {
9
- title: "Components/Modal",
9
+ title: "Components/Overlays/Modal",
10
10
  component: Modal,
11
11
  parameters: { layout: "padded" },
12
12
  args: {
@@ -57,7 +57,7 @@ const TRACKS: PickerOption[] = [
57
57
  ]
58
58
 
59
59
  const meta = {
60
- title: "Components/Picker",
60
+ title: "Components/Controls/Picker",
61
61
  component: Picker,
62
62
  parameters: { layout: "padded" },
63
63
  argTypes: {
@@ -14,7 +14,7 @@ import {
14
14
  import { Popover } from "./Popover.tsx"
15
15
 
16
16
  const meta = {
17
- title: "Components/Popover",
17
+ title: "Components/Overlays/Popover",
18
18
  component: Popover,
19
19
  parameters: { layout: "padded" },
20
20
  argTypes: { placement: placementArgType },
@@ -20,7 +20,7 @@ const DISC_THRESHOLDS: ProgressThreshold[] = [
20
20
  ]
21
21
 
22
22
  const meta = {
23
- title: "Components/ProgressBar",
23
+ title: "Components/Feedback/ProgressBar",
24
24
  component: ProgressBar,
25
25
  parameters: { layout: "padded" },
26
26
  argTypes: { intent: intentArgType },
@@ -173,7 +173,7 @@ const QueryBuilderHarness = ({
173
173
  }
174
174
 
175
175
  const meta = {
176
- title: "Components/QueryBuilder",
176
+ title: "Components/Controls/QueryBuilder",
177
177
  component: QueryBuilder,
178
178
  parameters: { layout: "padded" },
179
179
  args: {
@@ -24,7 +24,7 @@ const NAMING_ITEMS: RadioItem[] = [
24
24
  ]
25
25
 
26
26
  const meta = {
27
- title: "Components/RadioGroup",
27
+ title: "Components/Controls/RadioGroup",
28
28
  component: RadioGroup,
29
29
  parameters: { layout: "padded" },
30
30
  argTypes: { size: controlSizeArgType },
@@ -13,7 +13,7 @@ import {
13
13
  import { Rail } from "./Rail.tsx"
14
14
 
15
15
  const meta = {
16
- title: "Components/Rail",
16
+ title: "Components/Layout/Rail",
17
17
  component: Rail,
18
18
  parameters: { layout: "fullscreen" },
19
19
  argTypes: {
@@ -24,7 +24,7 @@ const COLUMN_ITEMS: SegmentedItem[] = [
24
24
  ]
25
25
 
26
26
  const meta = {
27
- title: "Components/SegmentedControl",
27
+ title: "Components/Controls/SegmentedControl",
28
28
  component: SegmentedControl,
29
29
  parameters: { layout: "padded" },
30
30
  argTypes: { size: controlSizeArgType },
@@ -41,13 +41,24 @@ const GROUPED: SelectItem[] = [
41
41
  ]
42
42
 
43
43
  const meta = {
44
- // `Deprecated/`, not `Components/`, and that is the whole point of
45
- // moving it: the sidebar is the first place an agent looks for a
46
- // picker, and a `Select` sitting between `Rail` and `Shell` reads
47
- // as a component in good standing. Nothing new gets one — `Picker`
48
- // is the drop-in. See the 2026-08-20 deprecation record.
49
- title: "Deprecated/Select",
44
+ title: "Components/Controls/Select",
50
45
  component: Select,
46
+ /**
47
+ * Deprecated — `Picker` is the drop-in. This is a **label, not a
48
+ * relocation**: the component still lives beside the other
49
+ * controls, where somebody looking for a picker will actually
50
+ * find it, and the sidebar paints a badge on it there. A
51
+ * `Deprecated/` section instead of a label hides the warning from
52
+ * everyone who does not already know to look for it, which is
53
+ * exactly backwards.
54
+ *
55
+ * Manager entries take the **intersection** of their children's
56
+ * tags, so tagging the meta puts the badge on the `Select` node
57
+ * itself, not just on each story underneath it.
58
+ * `sidebar.renderLabel` in `@charcuterie/storybook-config/manager`
59
+ * is what draws it.
60
+ */
61
+ tags: ["deprecated"],
51
62
  parameters: { layout: "padded" },
52
63
  argTypes: { size: controlSizeArgType },
53
64
  args: {
@@ -29,7 +29,7 @@ const CONTENT_WIDTH_OPTIONS = [
29
29
  ] as const
30
30
 
31
31
  const meta = {
32
- title: "Components/Shell",
32
+ title: "Components/Layout/Shell",
33
33
  component: Shell,
34
34
  parameters: { layout: "fullscreen" },
35
35
  argTypes: {
@@ -10,7 +10,7 @@ import { Card } from "../Card/Card.tsx"
10
10
  import { Skeleton } from "./Skeleton.tsx"
11
11
 
12
12
  const meta = {
13
- title: "Components/Skeleton",
13
+ title: "Components/Feedback/Skeleton",
14
14
  component: Skeleton,
15
15
  parameters: { layout: "padded" },
16
16
  args: { lineCount: 1, shape: "block" },
@@ -30,7 +30,7 @@ const CELL_CLASS =
30
30
  * every table gets wrong the same way.
31
31
  */
32
32
  const meta = {
33
- title: "Components/SortableTableHeader",
33
+ title: "Components/Data/SortableTableHeader",
34
34
  component: SortableTableHeader,
35
35
  parameters: { layout: "padded" },
36
36
  args: { onSort: noop },
@@ -11,7 +11,7 @@ import {
11
11
  import { Spinner } from "./Spinner.tsx"
12
12
 
13
13
  const meta = {
14
- title: "Components/Spinner",
14
+ title: "Components/Feedback/Spinner",
15
15
  component: Spinner,
16
16
  parameters: { layout: "padded" },
17
17
  argTypes: { size: controlSizeArgType },
@@ -22,7 +22,7 @@ const STICKERS = [
22
22
  ] as const
23
23
 
24
24
  const meta = {
25
- title: "Components/Swatch",
25
+ title: "Components/Controls/Swatch",
26
26
  component: Swatch,
27
27
  parameters: { layout: "padded" },
28
28
  argTypes: { color: { control: "color" } },
@@ -9,7 +9,7 @@ import {
9
9
  import { Switch } from "./Switch.tsx"
10
10
 
11
11
  const meta = {
12
- title: "Components/Switch",
12
+ title: "Components/Controls/Switch",
13
13
  component: Switch,
14
14
  parameters: { layout: "padded" },
15
15
  argTypes: { size: controlSizeArgType },
@@ -44,7 +44,7 @@ const BAY_TABS: TabItem[] = [
44
44
  ]
45
45
 
46
46
  const meta = {
47
- title: "Components/Tabs",
47
+ title: "Components/Layout/Tabs",
48
48
  component: Tabs,
49
49
  parameters: { layout: "padded" },
50
50
  argTypes: {
@@ -39,7 +39,7 @@ const INTENTS = [
39
39
  ] as const
40
40
 
41
41
  const meta = {
42
- title: "Components/TextLink",
42
+ title: "Components/Actions/TextLink",
43
43
  component: TextLink,
44
44
  parameters: { layout: "padded" },
45
45
  argTypes: {
@@ -20,7 +20,7 @@ const noop = () => undefined
20
20
  * is the documented way to hold one for an action anyway.
21
21
  */
22
22
  const meta = {
23
- title: "Components/Toast",
23
+ title: "Components/Overlays/Toast",
24
24
  component: Toast,
25
25
  parameters: { layout: "padded" },
26
26
  argTypes: { intent: intentArgType },
@@ -126,7 +126,7 @@ const Frame = ({
126
126
  )
127
127
 
128
128
  const meta = {
129
- title: "Components/Toolbar",
129
+ title: "Components/Actions/Toolbar",
130
130
  component: Toolbar,
131
131
  parameters: { layout: "padded" },
132
132
  argTypes: { size: controlSizeArgType },
@@ -11,7 +11,7 @@ import { SettingsIcon } from "../icons.storyHelpers.tsx"
11
11
  import { Tooltip } from "./Tooltip.tsx"
12
12
 
13
13
  const meta = {
14
- title: "Components/Tooltip",
14
+ title: "Components/Overlays/Tooltip",
15
15
  component: Tooltip,
16
16
  parameters: { layout: "centered" },
17
17
  argTypes: { placement: placementArgType },
@@ -19,7 +19,7 @@ import { VisuallyHidden } from "./VisuallyHidden.tsx"
19
19
  * loses translation.
20
20
  */
21
21
  const meta = {
22
- title: "Foundation/VisuallyHidden",
22
+ title: "Utilities/VisuallyHidden",
23
23
  component: VisuallyHidden,
24
24
  parameters: { layout: "padded" },
25
25
  } satisfies Meta<typeof VisuallyHidden>
@@ -98,6 +98,34 @@ blank line at the top and bottom of the block that reads as a rendering bug. A l
98
98
  stays, because hiding `-` turns an unordered list into indented prose — Obsidian substitutes
99
99
  a `•`, which means choosing a width, and this component has no business choosing one.
100
100
 
101
+ ## Tables render, and the caret edits them
102
+
103
+ <Canvas of={MarkdownEditorCodeMirrorStories.Tables} />
104
+
105
+ A table is the one construct on this surface whose meaning is *geometry*. Every other
106
+ decoration hangs on text CodeMirror draws anyway; a column cannot, because column two's text
107
+ is on four separate lines. So the pipes stop being drawn and a real `<table>` is drawn
108
+ instead — alignment from the delimiter row, and `**bold**`, `` `code` ``, links and images
109
+ rendering inside the cells exactly as they do in a paragraph.
110
+
111
+ **Click a cell and the markdown comes back with the caret in that cell.** That is the same
112
+ trade as a link or an image, and it is the only way in: you cannot edit a column you cannot
113
+ see. Arrow keys work too — Down from the line above enters at the top row, Up from below
114
+ enters at the bottom — because CodeMirror otherwise steps *over* a block widget and the
115
+ table would be unreachable without a mouse.
116
+
117
+ Three decisions worth knowing:
118
+
119
+ - **The widest row wins, not the header.** GFM drops cells past the header's width. An
120
+ editor that did the same would put text you typed on screen nowhere, so short rows gain
121
+ empty cells instead and nothing is ever hidden.
122
+ - **An empty cell keeps its column.** The parser emits no cell node for `| |`; the `|`
123
+ positions are what the columns are read from, so a blank cell does not shift the row.
124
+ - **A wide table scrolls inside its own block** rather than pushing the editor sideways.
125
+
126
+ `\|` — the only way to put a pipe *in* a cell — now conceals its backslash like any other
127
+ marker, in tables and everywhere else.
128
+
101
129
  ## Clicking a link opens it
102
130
 
103
131
  Obsidian's behaviour, not VS Code's: where the markup is hidden, a link that looks like a
@@ -47,8 +47,30 @@ ip -br addr show
47
47
  Replacement rails ordered from https://example.invalid/product?id=1234
48
48
  `
49
49
 
50
+ /**
51
+ * Alignment from the delimiter row, inline markup inside cells, a
52
+ * deliberately blank cell, and a row that is one column short —
53
+ * every table decision in one fixture.
54
+ */
55
+ const TABLES = `## Rack inventory
56
+
57
+ | Port | Goes to | **Speed** | Notes |
58
+ | :--- | :---: | ---: | --- |
59
+ | 1 | uplink | 10G | swapped \`2026-08-19\` |
60
+ | 2 | | 1G | see [the runbook](https://example.invalid/runbook) |
61
+ | 3 | shelf | 1G |
62
+ | 4 | ~~spare~~ | — | pulled \\| relabel |
63
+
64
+ A table with no outer pipes is still a table:
65
+
66
+ Disk | Size
67
+ --- | ---:
68
+ front-left | 18 TB
69
+ front-right | 18 TB
70
+ `
71
+
50
72
  const meta = {
51
- title: "Components/MarkdownEditorCodeMirror",
73
+ title: "Components/Controls/MarkdownEditorCodeMirror",
52
74
  component: MarkdownEditorCodeMirror,
53
75
  parameters: { layout: "padded" },
54
76
  args: {
@@ -145,3 +167,23 @@ export const CaretReveal: Story = {
145
167
  label: "Description",
146
168
  },
147
169
  }
170
+
171
+ /**
172
+ * The construct whose meaning is geometry.
173
+ *
174
+ * Everything else on this surface is a decoration hung on text that
175
+ * is drawn anyway — a column is not, because column two's text is
176
+ * on four separate lines. So the pipes stop being drawn and a real
177
+ * table is drawn instead, alignment and all, with `**bold**` and
178
+ * links rendering inside the cells like anywhere else.
179
+ *
180
+ * Click a cell and the markdown comes back with the caret in that
181
+ * cell — the same trade the rest of the surface makes for a link or
182
+ * an image.
183
+ */
184
+ export const Tables: Story = {
185
+ args: {
186
+ defaultValue: TABLES,
187
+ label: "Description",
188
+ },
189
+ }