@usefidel/design-system 0.3.0 → 0.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.
@@ -10,6 +10,6 @@
10
10
  "webapp.css"
11
11
  ],
12
12
  "storybookUrl": null,
13
- "packageVersion": "0.3.0",
14
- "resolvedSha": "e4915ceec1dba185cff3ba11756fee2676b48c5d"
13
+ "packageVersion": "0.5.0",
14
+ "resolvedSha": "03a49c2e8d888b7916f449d7801daa922c525737"
15
15
  }
package/dist/index.cjs CHANGED
@@ -128,69 +128,129 @@ function DiffSection({
128
128
  mismatchCount,
129
129
  elements,
130
130
  defaultExpanded = false,
131
+ headerless = false,
132
+ onElementToggle,
133
+ onElementHover,
131
134
  className,
132
135
  ...rest
133
136
  }) {
134
137
  const [expanded, setExpanded] = (0, import_react2.useState)(defaultExpanded);
138
+ const open = headerless || expanded;
135
139
  const count = mismatchCount ?? elements.reduce((sum, el) => sum + el.properties.length, 0);
136
- const classes = ["fds-diff-section", className].filter(Boolean).join(" ");
137
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: classes, "data-expanded": expanded ? "true" : void 0, ...rest, children: [
138
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
139
- "button",
140
- {
141
- type: "button",
142
- className: "fds-diff-section__header",
143
- "aria-expanded": expanded,
144
- onClick: () => setExpanded((v) => !v),
145
- children: [
146
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fds-diff-section__head-left", children: [
147
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Chevron2, { open: expanded }),
148
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__number", children: number }),
149
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__name", children: name })
150
- ] }),
151
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__count", children: plural(count, "mismatch", "mismatches") })
152
- ]
153
- }
154
- ),
155
- expanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__body", children: elements.map((el, ei) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__element", children: [
156
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__element-head", children: [
157
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__element-name", children: el.name }),
158
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__element-count", children: plural(el.properties.length, "property", "properties") })
159
- ] }),
160
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__lines", children: el.properties.map((line, li) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__line", children: [
161
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__prop", children: line.property }),
162
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fds-diff-section__delta", children: [
163
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__live", children: line.live }),
164
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Arrow, {}),
165
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__design", children: line.design })
166
- ] })
167
- ] }, `${line.property}-${li}`)) })
168
- ] }, `${el.name}-${ei}`)) })
169
- ] });
140
+ const classes = [
141
+ "fds-diff-section",
142
+ headerless && "fds-diff-section--flat",
143
+ className
144
+ ].filter(Boolean).join(" ");
145
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
146
+ "div",
147
+ {
148
+ className: classes,
149
+ "data-expanded": open ? "true" : void 0,
150
+ "data-flat": headerless ? "true" : void 0,
151
+ ...rest,
152
+ children: [
153
+ !headerless && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
154
+ "button",
155
+ {
156
+ type: "button",
157
+ className: "fds-diff-section__header",
158
+ "aria-expanded": expanded,
159
+ onClick: () => setExpanded((v) => !v),
160
+ children: [
161
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fds-diff-section__head-left", children: [
162
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Chevron2, { open: expanded }),
163
+ number != null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__number", children: number }),
164
+ name != null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__name", children: name })
165
+ ] }),
166
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__count", children: plural(count, "mismatch", "mismatches") })
167
+ ]
168
+ }
169
+ ),
170
+ open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__body", children: elements.map((el, ei) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
171
+ ElementRow,
172
+ {
173
+ element: el,
174
+ index: ei,
175
+ onToggle: onElementToggle,
176
+ onHover: onElementHover
177
+ },
178
+ `${el.name}-${ei}`
179
+ )) })
180
+ ]
181
+ }
182
+ );
170
183
  }
171
- function Chevron2({ open }) {
172
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
173
- "svg",
184
+ function ElementRow({
185
+ element,
186
+ index,
187
+ onToggle,
188
+ onHover
189
+ }) {
190
+ const hasDetail = element.detail != null;
191
+ const isControlled = element.open !== void 0;
192
+ const [internalOpen, setInternalOpen] = (0, import_react2.useState)(element.defaultOpen ?? false);
193
+ const open = isControlled ? !!element.open : internalOpen;
194
+ const toggle = () => {
195
+ const next = !open;
196
+ if (!isControlled) setInternalOpen(next);
197
+ onToggle?.(index, next);
198
+ };
199
+ const lines = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
200
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__element-head-inner", children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__element-name", children: element.name }),
202
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__element-count", children: plural(element.properties.length, "property", "properties") }),
203
+ hasDetail && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Chevron2, { open, className: "fds-diff-section__element-caret" })
204
+ ] }),
205
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__lines", children: element.properties.map((line, li) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fds-diff-section__line", children: [
206
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__prop", children: line.property }),
207
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fds-diff-section__delta", children: [
208
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__live", children: line.live }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Arrow, {}),
210
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fds-diff-section__design", children: line.design })
211
+ ] })
212
+ ] }, `${line.property}-${li}`)) })
213
+ ] });
214
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
215
+ "div",
174
216
  {
175
- className: `fds-diff-section__chevron${open ? " fds-diff-section__chevron--open" : ""}`,
176
- width: "12",
177
- height: "12",
178
- viewBox: "0 0 12 12",
179
- fill: "none",
180
- "aria-hidden": "true",
181
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
182
- "path",
183
- {
184
- d: "M4.5 3 8 6l-3.5 3",
185
- stroke: "currentColor",
186
- strokeWidth: "1.3",
187
- strokeLinecap: "round",
188
- strokeLinejoin: "round"
189
- }
190
- )
217
+ className: "fds-diff-section__element",
218
+ "data-open": hasDetail && open ? "true" : void 0,
219
+ onMouseEnter: onHover ? () => onHover(index, true) : void 0,
220
+ onMouseLeave: onHover ? () => onHover(index, false) : void 0,
221
+ children: [
222
+ hasDetail ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
223
+ "button",
224
+ {
225
+ type: "button",
226
+ className: "fds-diff-section__element-head fds-diff-section__element-head--button",
227
+ "aria-expanded": open,
228
+ onClick: toggle,
229
+ children: lines
230
+ }
231
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__element-head", children: lines }),
232
+ hasDetail && open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fds-diff-section__detail", children: element.detail })
233
+ ]
191
234
  }
192
235
  );
193
236
  }
237
+ function Chevron2({ open, className }) {
238
+ const classes = [
239
+ "fds-diff-section__chevron",
240
+ open && "fds-diff-section__chevron--open",
241
+ className
242
+ ].filter(Boolean).join(" ");
243
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", className: classes, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
244
+ "path",
245
+ {
246
+ d: "M4.5 3 8 6l-3.5 3",
247
+ stroke: "currentColor",
248
+ strokeWidth: "1.3",
249
+ strokeLinecap: "round",
250
+ strokeLinejoin: "round"
251
+ }
252
+ ) });
253
+ }
194
254
  function Arrow() {
195
255
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
196
256
  "svg",
package/dist/index.css CHANGED
@@ -310,14 +310,45 @@
310
310
  .fds-diff-section__element {
311
311
  display: flex;
312
312
  flex-direction: column;
313
+ }
314
+ .fds-diff-section__element-head {
315
+ display: flex;
316
+ flex-direction: column;
313
317
  gap: 14px;
318
+ width: 100%;
314
319
  padding: 6px var(--spacing-5) 6px var(--spacing-6);
315
320
  }
316
- .fds-diff-section__element-head {
321
+ .fds-diff-section__element-head--button {
322
+ border: 0;
323
+ background: none;
324
+ font: inherit;
325
+ color: inherit;
326
+ text-align: left;
327
+ cursor: pointer;
328
+ transition: background var(--duration-quick, 150ms) ease;
329
+ }
330
+ .fds-diff-section__element-head--button:hover {
331
+ background: var(--color-surface-hover);
332
+ }
333
+ .fds-diff-section__element-head-inner {
317
334
  display: flex;
318
335
  align-items: center;
319
336
  gap: var(--spacing-2);
320
337
  }
338
+ .fds-diff-section__element-caret {
339
+ margin-left: auto;
340
+ flex-shrink: 0;
341
+ color: var(--color-text-muted);
342
+ }
343
+ .fds-diff-section__detail {
344
+ padding: 0 var(--spacing-5) var(--spacing-3) var(--spacing-6);
345
+ }
346
+ .fds-diff-section--flat .fds-diff-section__body {
347
+ gap: 0;
348
+ }
349
+ .fds-diff-section--flat .fds-diff-section__element + .fds-diff-section__element {
350
+ border-top: 1px solid var(--color-border-light);
351
+ }
321
352
  .fds-diff-section__element-name {
322
353
  font-weight: var(--font-weight-medium);
323
354
  font-size: 14px;
package/dist/index.d.cts CHANGED
@@ -74,12 +74,29 @@ interface DiffElement {
74
74
  name: string;
75
75
  /** Property mismatches for this element. */
76
76
  properties: DiffPropertyLine[];
77
+ /**
78
+ * Optional drill-down content. When provided, the element row becomes a
79
+ * toggle: clicking it reveals this node beneath the property deltas. Use it
80
+ * to host a richer detail view (copy actions, triage, verdicts) while the
81
+ * mono deltas stay as the at-a-glance summary. Presentational — the caller
82
+ * owns whatever lives inside.
83
+ */
84
+ detail?: ReactNode;
85
+ /** When the element has `detail`, render it open on first paint (uncontrolled). @default false */
86
+ defaultOpen?: boolean;
87
+ /**
88
+ * Controlled open state. When defined, the row is controlled: the component
89
+ * renders this value and never keeps its own expand state — the caller owns
90
+ * it via `onElementToggle` (so drawer selection can drive, e.g., a matching
91
+ * screenshot highlight). Leave undefined for uncontrolled (`defaultOpen`).
92
+ */
93
+ open?: boolean;
77
94
  }
78
95
  interface DiffSectionProps extends HTMLAttributes<HTMLDivElement> {
79
- /** Ordinal shown before the name, e.g. "01". Caller controls padding. */
80
- number: string;
81
- /** Section name, e.g. "Nav". */
82
- name: string;
96
+ /** Ordinal shown before the name, e.g. "01". Omitted in `headerless` mode. */
97
+ number?: string;
98
+ /** Section name, e.g. "Nav". Omitted in `headerless` mode. */
99
+ name?: string;
83
100
  /**
84
101
  * Count shown on the right of the header. Defaults to the total number of
85
102
  * property lines across all elements.
@@ -87,17 +104,38 @@ interface DiffSectionProps extends HTMLAttributes<HTMLDivElement> {
87
104
  mismatchCount?: number;
88
105
  /** Elements in this section, each with its own property mismatches. */
89
106
  elements: DiffElement[];
90
- /** Render expanded on first paint. @default false */
107
+ /** Render expanded on first paint. Ignored in `headerless` mode. @default false */
91
108
  defaultExpanded?: boolean;
109
+ /**
110
+ * Drop the numbered, collapsible section header and render the element list
111
+ * directly — a flat drawer with no section grouping. `number`/`name`/
112
+ * `mismatchCount` are ignored. @default false
113
+ */
114
+ headerless?: boolean;
115
+ /**
116
+ * Called when an element with `detail` is toggled. `index` is the element's
117
+ * position in `elements`. Pair with `DiffElement.open` to control expansion
118
+ * from the parent (e.g. to sync a screenshot highlight with drawer selection).
119
+ */
120
+ onElementToggle?: (index: number, open: boolean) => void;
121
+ /** Called when the pointer enters (`true`) or leaves (`false`) an element row. */
122
+ onElementHover?: (index: number, hovering: boolean) => void;
92
123
  }
93
124
  /**
94
- * Fidel design-system DiffSection — a numbered, collapsible section in the
95
- * mismatch drawer. Collapsed it shows just the header (`01 Nav · 2
96
- * mismatches`); expanded it lists each element and its property deltas.
125
+ * Fidel design-system DiffSection — the mismatch drawer's section.
126
+ *
127
+ * Two shapes:
128
+ * - Grouped (default): a numbered, collapsible header (`01 Nav · 2
129
+ * mismatches`) over its elements.
130
+ * - `headerless`: just the element list, for a flat drawer with no section
131
+ * grouping.
132
+ *
133
+ * Each element lists its property deltas as `live → design` mono rows, and may
134
+ * carry a `detail` node that expands beneath the deltas as a drill-down.
97
135
  *
98
136
  * Presentational only — no diff taxonomy. Framing is Live → Design (founder
99
137
  * decision 2026-04-24): each delta reads `live → design`.
100
138
  */
101
- declare function DiffSection({ number, name, mismatchCount, elements, defaultExpanded, className, ...rest }: DiffSectionProps): react.JSX.Element;
139
+ declare function DiffSection({ number, name, mismatchCount, elements, defaultExpanded, headerless, onElementToggle, onElementHover, className, ...rest }: DiffSectionProps): react.JSX.Element;
102
140
 
103
141
  export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty };
package/dist/index.d.ts CHANGED
@@ -74,12 +74,29 @@ interface DiffElement {
74
74
  name: string;
75
75
  /** Property mismatches for this element. */
76
76
  properties: DiffPropertyLine[];
77
+ /**
78
+ * Optional drill-down content. When provided, the element row becomes a
79
+ * toggle: clicking it reveals this node beneath the property deltas. Use it
80
+ * to host a richer detail view (copy actions, triage, verdicts) while the
81
+ * mono deltas stay as the at-a-glance summary. Presentational — the caller
82
+ * owns whatever lives inside.
83
+ */
84
+ detail?: ReactNode;
85
+ /** When the element has `detail`, render it open on first paint (uncontrolled). @default false */
86
+ defaultOpen?: boolean;
87
+ /**
88
+ * Controlled open state. When defined, the row is controlled: the component
89
+ * renders this value and never keeps its own expand state — the caller owns
90
+ * it via `onElementToggle` (so drawer selection can drive, e.g., a matching
91
+ * screenshot highlight). Leave undefined for uncontrolled (`defaultOpen`).
92
+ */
93
+ open?: boolean;
77
94
  }
78
95
  interface DiffSectionProps extends HTMLAttributes<HTMLDivElement> {
79
- /** Ordinal shown before the name, e.g. "01". Caller controls padding. */
80
- number: string;
81
- /** Section name, e.g. "Nav". */
82
- name: string;
96
+ /** Ordinal shown before the name, e.g. "01". Omitted in `headerless` mode. */
97
+ number?: string;
98
+ /** Section name, e.g. "Nav". Omitted in `headerless` mode. */
99
+ name?: string;
83
100
  /**
84
101
  * Count shown on the right of the header. Defaults to the total number of
85
102
  * property lines across all elements.
@@ -87,17 +104,38 @@ interface DiffSectionProps extends HTMLAttributes<HTMLDivElement> {
87
104
  mismatchCount?: number;
88
105
  /** Elements in this section, each with its own property mismatches. */
89
106
  elements: DiffElement[];
90
- /** Render expanded on first paint. @default false */
107
+ /** Render expanded on first paint. Ignored in `headerless` mode. @default false */
91
108
  defaultExpanded?: boolean;
109
+ /**
110
+ * Drop the numbered, collapsible section header and render the element list
111
+ * directly — a flat drawer with no section grouping. `number`/`name`/
112
+ * `mismatchCount` are ignored. @default false
113
+ */
114
+ headerless?: boolean;
115
+ /**
116
+ * Called when an element with `detail` is toggled. `index` is the element's
117
+ * position in `elements`. Pair with `DiffElement.open` to control expansion
118
+ * from the parent (e.g. to sync a screenshot highlight with drawer selection).
119
+ */
120
+ onElementToggle?: (index: number, open: boolean) => void;
121
+ /** Called when the pointer enters (`true`) or leaves (`false`) an element row. */
122
+ onElementHover?: (index: number, hovering: boolean) => void;
92
123
  }
93
124
  /**
94
- * Fidel design-system DiffSection — a numbered, collapsible section in the
95
- * mismatch drawer. Collapsed it shows just the header (`01 Nav · 2
96
- * mismatches`); expanded it lists each element and its property deltas.
125
+ * Fidel design-system DiffSection — the mismatch drawer's section.
126
+ *
127
+ * Two shapes:
128
+ * - Grouped (default): a numbered, collapsible header (`01 Nav · 2
129
+ * mismatches`) over its elements.
130
+ * - `headerless`: just the element list, for a flat drawer with no section
131
+ * grouping.
132
+ *
133
+ * Each element lists its property deltas as `live → design` mono rows, and may
134
+ * carry a `detail` node that expands beneath the deltas as a drill-down.
97
135
  *
98
136
  * Presentational only — no diff taxonomy. Framing is Live → Design (founder
99
137
  * decision 2026-04-24): each delta reads `live → design`.
100
138
  */
101
- declare function DiffSection({ number, name, mismatchCount, elements, defaultExpanded, className, ...rest }: DiffSectionProps): react.JSX.Element;
139
+ declare function DiffSection({ number, name, mismatchCount, elements, defaultExpanded, headerless, onElementToggle, onElementHover, className, ...rest }: DiffSectionProps): react.JSX.Element;
102
140
 
103
141
  export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, type DiffElement, type DiffPropertyLine, DiffSection, type DiffSectionProps, MismatchCard, type MismatchCardProps, type MismatchProperty };
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ function Chevron({ direction }) {
90
90
 
91
91
  // src/components/DiffSection/DiffSection.tsx
92
92
  import { useState as useState2 } from "react";
93
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
93
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
94
94
  function plural(n, one, many) {
95
95
  return `${n} ${n === 1 ? one : many}`;
96
96
  }
@@ -100,69 +100,129 @@ function DiffSection({
100
100
  mismatchCount,
101
101
  elements,
102
102
  defaultExpanded = false,
103
+ headerless = false,
104
+ onElementToggle,
105
+ onElementHover,
103
106
  className,
104
107
  ...rest
105
108
  }) {
106
109
  const [expanded, setExpanded] = useState2(defaultExpanded);
110
+ const open = headerless || expanded;
107
111
  const count = mismatchCount ?? elements.reduce((sum, el) => sum + el.properties.length, 0);
108
- const classes = ["fds-diff-section", className].filter(Boolean).join(" ");
109
- return /* @__PURE__ */ jsxs3("div", { className: classes, "data-expanded": expanded ? "true" : void 0, ...rest, children: [
110
- /* @__PURE__ */ jsxs3(
111
- "button",
112
- {
113
- type: "button",
114
- className: "fds-diff-section__header",
115
- "aria-expanded": expanded,
116
- onClick: () => setExpanded((v) => !v),
117
- children: [
118
- /* @__PURE__ */ jsxs3("span", { className: "fds-diff-section__head-left", children: [
119
- /* @__PURE__ */ jsx3(Chevron2, { open: expanded }),
120
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__number", children: number }),
121
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__name", children: name })
122
- ] }),
123
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__count", children: plural(count, "mismatch", "mismatches") })
124
- ]
125
- }
126
- ),
127
- expanded && /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__body", children: elements.map((el, ei) => /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__element", children: [
128
- /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__element-head", children: [
129
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__element-name", children: el.name }),
130
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__element-count", children: plural(el.properties.length, "property", "properties") })
131
- ] }),
132
- /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__lines", children: el.properties.map((line, li) => /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__line", children: [
133
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__prop", children: line.property }),
134
- /* @__PURE__ */ jsxs3("span", { className: "fds-diff-section__delta", children: [
135
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__live", children: line.live }),
136
- /* @__PURE__ */ jsx3(Arrow, {}),
137
- /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__design", children: line.design })
138
- ] })
139
- ] }, `${line.property}-${li}`)) })
140
- ] }, `${el.name}-${ei}`)) })
141
- ] });
112
+ const classes = [
113
+ "fds-diff-section",
114
+ headerless && "fds-diff-section--flat",
115
+ className
116
+ ].filter(Boolean).join(" ");
117
+ return /* @__PURE__ */ jsxs3(
118
+ "div",
119
+ {
120
+ className: classes,
121
+ "data-expanded": open ? "true" : void 0,
122
+ "data-flat": headerless ? "true" : void 0,
123
+ ...rest,
124
+ children: [
125
+ !headerless && /* @__PURE__ */ jsxs3(
126
+ "button",
127
+ {
128
+ type: "button",
129
+ className: "fds-diff-section__header",
130
+ "aria-expanded": expanded,
131
+ onClick: () => setExpanded((v) => !v),
132
+ children: [
133
+ /* @__PURE__ */ jsxs3("span", { className: "fds-diff-section__head-left", children: [
134
+ /* @__PURE__ */ jsx3(Chevron2, { open: expanded }),
135
+ number != null && /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__number", children: number }),
136
+ name != null && /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__name", children: name })
137
+ ] }),
138
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__count", children: plural(count, "mismatch", "mismatches") })
139
+ ]
140
+ }
141
+ ),
142
+ open && /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__body", children: elements.map((el, ei) => /* @__PURE__ */ jsx3(
143
+ ElementRow,
144
+ {
145
+ element: el,
146
+ index: ei,
147
+ onToggle: onElementToggle,
148
+ onHover: onElementHover
149
+ },
150
+ `${el.name}-${ei}`
151
+ )) })
152
+ ]
153
+ }
154
+ );
142
155
  }
143
- function Chevron2({ open }) {
144
- return /* @__PURE__ */ jsx3(
145
- "svg",
156
+ function ElementRow({
157
+ element,
158
+ index,
159
+ onToggle,
160
+ onHover
161
+ }) {
162
+ const hasDetail = element.detail != null;
163
+ const isControlled = element.open !== void 0;
164
+ const [internalOpen, setInternalOpen] = useState2(element.defaultOpen ?? false);
165
+ const open = isControlled ? !!element.open : internalOpen;
166
+ const toggle = () => {
167
+ const next = !open;
168
+ if (!isControlled) setInternalOpen(next);
169
+ onToggle?.(index, next);
170
+ };
171
+ const lines = /* @__PURE__ */ jsxs3(Fragment, { children: [
172
+ /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__element-head-inner", children: [
173
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__element-name", children: element.name }),
174
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__element-count", children: plural(element.properties.length, "property", "properties") }),
175
+ hasDetail && /* @__PURE__ */ jsx3(Chevron2, { open, className: "fds-diff-section__element-caret" })
176
+ ] }),
177
+ /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__lines", children: element.properties.map((line, li) => /* @__PURE__ */ jsxs3("div", { className: "fds-diff-section__line", children: [
178
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__prop", children: line.property }),
179
+ /* @__PURE__ */ jsxs3("span", { className: "fds-diff-section__delta", children: [
180
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__live", children: line.live }),
181
+ /* @__PURE__ */ jsx3(Arrow, {}),
182
+ /* @__PURE__ */ jsx3("span", { className: "fds-diff-section__design", children: line.design })
183
+ ] })
184
+ ] }, `${line.property}-${li}`)) })
185
+ ] });
186
+ return /* @__PURE__ */ jsxs3(
187
+ "div",
146
188
  {
147
- className: `fds-diff-section__chevron${open ? " fds-diff-section__chevron--open" : ""}`,
148
- width: "12",
149
- height: "12",
150
- viewBox: "0 0 12 12",
151
- fill: "none",
152
- "aria-hidden": "true",
153
- children: /* @__PURE__ */ jsx3(
154
- "path",
155
- {
156
- d: "M4.5 3 8 6l-3.5 3",
157
- stroke: "currentColor",
158
- strokeWidth: "1.3",
159
- strokeLinecap: "round",
160
- strokeLinejoin: "round"
161
- }
162
- )
189
+ className: "fds-diff-section__element",
190
+ "data-open": hasDetail && open ? "true" : void 0,
191
+ onMouseEnter: onHover ? () => onHover(index, true) : void 0,
192
+ onMouseLeave: onHover ? () => onHover(index, false) : void 0,
193
+ children: [
194
+ hasDetail ? /* @__PURE__ */ jsx3(
195
+ "button",
196
+ {
197
+ type: "button",
198
+ className: "fds-diff-section__element-head fds-diff-section__element-head--button",
199
+ "aria-expanded": open,
200
+ onClick: toggle,
201
+ children: lines
202
+ }
203
+ ) : /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__element-head", children: lines }),
204
+ hasDetail && open && /* @__PURE__ */ jsx3("div", { className: "fds-diff-section__detail", children: element.detail })
205
+ ]
163
206
  }
164
207
  );
165
208
  }
209
+ function Chevron2({ open, className }) {
210
+ const classes = [
211
+ "fds-diff-section__chevron",
212
+ open && "fds-diff-section__chevron--open",
213
+ className
214
+ ].filter(Boolean).join(" ");
215
+ return /* @__PURE__ */ jsx3("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", className: classes, children: /* @__PURE__ */ jsx3(
216
+ "path",
217
+ {
218
+ d: "M4.5 3 8 6l-3.5 3",
219
+ stroke: "currentColor",
220
+ strokeWidth: "1.3",
221
+ strokeLinecap: "round",
222
+ strokeLinejoin: "round"
223
+ }
224
+ ) });
225
+ }
166
226
  function Arrow() {
167
227
  return /* @__PURE__ */ jsx3(
168
228
  "svg",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefidel/design-system",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Fidel brand design tokens + React component library — DTCG token source of truth, generated per-surface CSS + Tailwind-v4 parser-compatibility output, and on-brand React components (Button, MismatchCard, DiffSection).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",