@godxjp/ui 31.28.4 → 31.30.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 (72) hide show
  1. package/agent/START-HERE.md +5 -5
  2. package/agent/components/AppLauncher.json +5 -0
  3. package/agent/components/Avatar.json +1 -0
  4. package/agent/components/Dialog.json +3 -1
  5. package/agent/components/Legend.json +1 -1
  6. package/agent/components/PasswordStrength.json +2 -2
  7. package/agent/components/RangeTimeline.json +28 -1
  8. package/agent/components/ResponsiveGrid.json +7 -0
  9. package/agent/components/Sidebar.json +5 -0
  10. package/agent/components/SortableList.json +78 -0
  11. package/agent/components/TopbarItem.json +1 -1
  12. package/agent/components-index.json +5 -0
  13. package/agent/components.json +131 -6
  14. package/agent/index.json +6 -6
  15. package/agent/llms.txt +6 -6
  16. package/agent/tokens.json +122 -2
  17. package/dist/components/data-display/legend.d.ts +3 -2
  18. package/dist/components/data-display/legend.js +10 -1
  19. package/dist/components/data-display/range-timeline.d.ts +93 -2
  20. package/dist/components/data-display/range-timeline.js +220 -58
  21. package/dist/components/data-entry/index.d.ts +2 -0
  22. package/dist/components/data-entry/index.js +2 -0
  23. package/dist/components/data-entry/password-strength.d.ts +15 -3
  24. package/dist/components/data-entry/password-strength.js +16 -34
  25. package/dist/components/data-entry/sortable-list.d.ts +10 -0
  26. package/dist/components/data-entry/sortable-list.js +237 -0
  27. package/dist/components/feedback/dialog.d.ts +8 -2
  28. package/dist/components/feedback/dialog.js +8 -1
  29. package/dist/components/feedback/sheet.js +1 -1
  30. package/dist/components/layout/app-launcher.d.ts +1 -1
  31. package/dist/components/layout/app-launcher.js +18 -14
  32. package/dist/components/layout/responsive-grid.d.ts +17 -2
  33. package/dist/components/layout/responsive-grid.js +17 -4
  34. package/dist/components/layout/sidebar.d.ts +1 -1
  35. package/dist/components/layout/sidebar.js +3 -0
  36. package/dist/components/layout/topbar-item.js +8 -2
  37. package/dist/components/ui/avatar.d.ts +3 -1
  38. package/dist/components/ui/avatar.js +2 -1
  39. package/dist/contracts/measurement.json +1 -1
  40. package/dist/i18n/messages/en.json +31 -1
  41. package/dist/i18n/messages/ja.json +31 -1
  42. package/dist/i18n/messages/vi.json +31 -1
  43. package/dist/props/components/data-display.prop.d.ts +18 -0
  44. package/dist/props/components/data-entry.prop.d.ts +34 -0
  45. package/dist/props/components/feedback.prop.d.ts +6 -1
  46. package/dist/props/components/layout.prop.d.ts +22 -0
  47. package/dist/props/registry.d.ts +33 -2
  48. package/dist/props/registry.js +44 -1
  49. package/dist/styles/control.css +50 -0
  50. package/dist/styles/data-display-layout.css +139 -2
  51. package/dist/styles/dialog-layout.css +14 -1
  52. package/dist/styles/layers.json +6 -1
  53. package/dist/styles/layout.css +29 -7
  54. package/dist/styles/shell-layout.css +26 -0
  55. package/dist/tokens/components/control.css +5 -0
  56. package/dist/tokens/components/data-display.css +12 -0
  57. package/dist/tokens/components/feedback.css +6 -0
  58. package/dist/tokens/components/shell.css +6 -0
  59. package/dist/tokens/semantic/layout.css +1 -4
  60. package/docs/FRAME-COVERAGE-REPORT.md +4 -3
  61. package/docs/data-display/avatar.tsx +32 -0
  62. package/docs/data-display/timeline.tsx +84 -1
  63. package/docs/data-entry/sortable-list.tsx +76 -0
  64. package/docs/feedback/dialog.tsx +37 -1
  65. package/docs/i18n/messages/en.json +62 -0
  66. package/docs/i18n/messages/ja.json +62 -0
  67. package/docs/i18n/messages/vi.json +62 -0
  68. package/docs/layout/app-launcher.tsx +17 -1
  69. package/docs/layout/responsive-grid.tsx +23 -0
  70. package/docs/layout/sidebar.tsx +24 -0
  71. package/docs/layout/topbar-item.tsx +11 -0
  72. package/package.json +2 -2
@@ -4,8 +4,18 @@ import * as React from "react";
4
4
  import { useTranslation } from "../../i18n/use-translation.js";
5
5
  import { ChevronDown, ChevronLeft, ChevronRight, GripVertical } from "lucide-react";
6
6
  import { Button } from "../general/button.js";
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
8
+ import { CONTRAST_PIVOT, relativeLuminance } from "../../app/tenant-theme.js";
7
9
  import { Text } from "../general/typography.js";
8
10
  import { cn } from "../../lib/utils.js";
11
+ function inkOn(color) {
12
+ if (!color) return void 0;
13
+ const luminance = relativeLuminance(color);
14
+ if (luminance == null) return void 0;
15
+ return luminance > CONTRAST_PIVOT ? "black" : "white";
16
+ }
17
+ const LABEL_WIDTH_STEP = 8;
18
+ const LABEL_WIDTH_BIG_STEP = 32;
9
19
  const RangeTimeline = React.forwardRef(
10
20
  function RangeTimeline2({
11
21
  label,
@@ -20,10 +30,30 @@ const RangeTimeline = React.forwardRef(
20
30
  defaultExpandedValues,
21
31
  onExpandedValuesChange,
22
32
  sticky = false,
33
+ resizableLabel = false,
34
+ labelWidth,
35
+ defaultLabelWidth,
36
+ onLabelWidthChange,
37
+ busy = false,
23
38
  className,
24
39
  ...props
25
40
  }, ref) {
26
41
  const { t } = useTranslation();
42
+ const resize = resizableLabel ? {
43
+ min: typeof resizableLabel === "object" && resizableLabel.min || 160,
44
+ max: typeof resizableLabel === "object" && resizableLabel.max || 640
45
+ } : null;
46
+ const [ownLabelWidth, setOwnLabelWidth] = React.useState(defaultLabelWidth);
47
+ const currentLabelWidth = labelWidth ?? ownLabelWidth;
48
+ const labelCell = React.useRef(null);
49
+ const labelDrag = React.useRef(null);
50
+ const setLabelWidth = (next) => {
51
+ if (!resize) return;
52
+ const bounded = Math.round(Math.min(resize.max, Math.max(resize.min, next)));
53
+ if (labelWidth === void 0) setOwnLabelWidth(bounded);
54
+ onLabelWidthChange?.(bounded);
55
+ };
56
+ const measuredLabelWidth = () => currentLabelWidth ?? Math.round(labelCell.current?.getBoundingClientRect().width ?? 0);
27
57
  const reactId = React.useId();
28
58
  const depthOf = (row) => Math.max(0, Math.floor(row.depth ?? 0));
29
59
  const nested = rows.some((row) => depthOf(row) > 0);
@@ -85,7 +115,7 @@ const RangeTimeline = React.forwardRef(
85
115
  const row = rows.find((row2) => row2.id === current.id);
86
116
  const delta = deltaAt(event.clientX);
87
117
  cancel();
88
- if (!row) return;
118
+ if (!row || row.start === null || row.end === null) return;
89
119
  const bounded = current.edge === "start" ? Math.min(delta, row.end - row.start) : Math.max(delta, row.start - row.end);
90
120
  if (bounded) onRangeChange?.(row.id, current.edge, bounded);
91
121
  };
@@ -107,7 +137,53 @@ const RangeTimeline = React.forwardRef(
107
137
  )) })
108
138
  ] }),
109
139
  /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-header", children: [
110
- /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", children: /* @__PURE__ */ jsx(Text, { weight: "bold", children: label }) }),
140
+ /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-label", ref: labelCell, children: [
141
+ /* @__PURE__ */ jsx(Text, { weight: "bold", children: label }),
142
+ resize && /* @__PURE__ */ jsx(
143
+ "div",
144
+ {
145
+ role: "separator",
146
+ tabIndex: 0,
147
+ className: "ui-range-timeline-label-resizer",
148
+ "aria-orientation": "vertical",
149
+ "aria-label": t("rangeTimeline.resizeLabel"),
150
+ "aria-valuemin": resize.min,
151
+ "aria-valuemax": resize.max,
152
+ "aria-valuenow": currentLabelWidth ?? void 0,
153
+ onPointerDown: (event) => {
154
+ if (event.button !== 0) return;
155
+ event.preventDefault();
156
+ event.currentTarget.setPointerCapture(event.pointerId);
157
+ labelDrag.current = {
158
+ x: event.clientX,
159
+ width: measuredLabelWidth(),
160
+ direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
161
+ };
162
+ },
163
+ onPointerMove: (event) => {
164
+ const current = labelDrag.current;
165
+ if (current)
166
+ setLabelWidth(current.width + (event.clientX - current.x) * current.direction);
167
+ },
168
+ onPointerUp: () => labelDrag.current = null,
169
+ onPointerCancel: () => labelDrag.current = null,
170
+ onLostPointerCapture: () => labelDrag.current = null,
171
+ onKeyDown: (event) => {
172
+ const step = event.shiftKey ? LABEL_WIDTH_BIG_STEP : LABEL_WIDTH_STEP;
173
+ const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
174
+ let next = null;
175
+ if (event.key === "ArrowRight") next = measuredLabelWidth() + step * direction;
176
+ else if (event.key === "ArrowLeft")
177
+ next = measuredLabelWidth() - step * direction;
178
+ else if (event.key === "Home") next = resize.min;
179
+ else if (event.key === "End") next = resize.max;
180
+ if (next === null) return;
181
+ event.preventDefault();
182
+ setLabelWidth(next);
183
+ }
184
+ }
185
+ )
186
+ ] }),
111
187
  /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-columns", ref: track, children: columns.map((column, index) => /* @__PURE__ */ jsx(
112
188
  "div",
113
189
  {
@@ -117,7 +193,8 @@ const RangeTimeline = React.forwardRef(
117
193
  },
118
194
  index
119
195
  )) })
120
- ] })
196
+ ] }),
197
+ busy && /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-busy", "aria-hidden": "true" })
121
198
  ] });
122
199
  const body = /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-body", role: nested ? "list" : void 0, children: [
123
200
  (bordered || columns.some((column) => column.muted)) && // Decorative: the same unit tracks as the header columns, laid once behind every row so
@@ -136,10 +213,17 @@ const RangeTimeline = React.forwardRef(
136
213
  ] }),
137
214
  visibleRows.map((row, index) => {
138
215
  const idBase = `${reactId}-row-${rows.indexOf(row)}`;
216
+ const dated = row.start !== null && row.end !== null;
217
+ const rowStart = row.start ?? 0, rowEnd = row.end ?? 0;
139
218
  const delta = preview?.id === row.id ? preview.delta : 0;
140
- const start = Math.min(row.end, row.start + (preview?.edge === "start" ? delta : 0));
141
- const end = Math.max(row.start, row.end + (preview?.edge === "end" ? delta : 0));
219
+ const start = Math.min(rowEnd, rowStart + (preview?.edge === "start" ? delta : 0));
220
+ const end = Math.max(rowStart, rowEnd + (preview?.edge === "end" ? delta : 0));
142
221
  const left = Math.max(0, start), right = Math.min(units, end + 1);
222
+ const plan = dated && row.plan && row.plan.start !== null && row.plan.end !== null ? row.plan : null;
223
+ const planLeft = plan ? Math.max(0, plan.start) : 0, planRight = plan ? Math.min(units, plan.end + 1) : 0;
224
+ const overrun = plan !== null && end > plan.end;
225
+ const early = plan !== null && end < plan.end;
226
+ const overrunLeft = overrun ? Math.max(left, Math.min(units, plan.end + 1)) : 0;
143
227
  return /* @__PURE__ */ jsxs(
144
228
  "div",
145
229
  {
@@ -182,7 +266,14 @@ const RangeTimeline = React.forwardRef(
182
266
  /* @__PURE__ */ jsx("div", { id: `${idBase}-label`, className: "ui-range-timeline-label-content", children: row.label })
183
267
  ]
184
268
  }
185
- ) : /* @__PURE__ */ jsx("div", { className: "ui-range-timeline-label", children: row.label }),
269
+ ) : /* @__PURE__ */ jsx(
270
+ "div",
271
+ {
272
+ className: "ui-range-timeline-label",
273
+ id: row.tooltip != null ? `${idBase}-label` : void 0,
274
+ children: row.label
275
+ }
276
+ ),
186
277
  /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-track", children: [
187
278
  today != null && today >= 0 && today < units && /* @__PURE__ */ jsx(
188
279
  "span",
@@ -192,71 +283,135 @@ const RangeTimeline = React.forwardRef(
192
283
  style: { insetInlineStart: `${(today + 0.5) / units * 100}%` }
193
284
  }
194
285
  ),
195
- right <= left && (end < 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "before", children: [
286
+ !dated && /* @__PURE__ */ jsx("span", { className: "ui-range-timeline-outside", "data-direction": "none", children: /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.noDates") }) }),
287
+ dated && right <= left && (end < 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "before", children: [
196
288
  /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true", className: "ui-range-timeline-outside-icon" }),
197
289
  /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideBefore") })
198
290
  ] }) : /* @__PURE__ */ jsxs("span", { className: "ui-range-timeline-outside", "data-direction": "after", children: [
199
291
  /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", children: t("rangeTimeline.outsideAfter") }),
200
292
  /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true", className: "ui-range-timeline-outside-icon" })
201
293
  ] })),
202
- right > left && /* @__PURE__ */ jsx(
294
+ plan && planRight > planLeft && /* @__PURE__ */ jsx(
295
+ "span",
296
+ {
297
+ "aria-hidden": "true",
298
+ className: "ui-range-timeline-plan",
299
+ style: {
300
+ insetInlineStart: `${planLeft / units * 100}%`,
301
+ inlineSize: `${(planRight - planLeft) / units * 100}%`
302
+ }
303
+ }
304
+ ),
305
+ overrun && right > overrunLeft && /* @__PURE__ */ jsxs(
306
+ "span",
307
+ {
308
+ className: "ui-range-timeline-overrun",
309
+ style: {
310
+ insetInlineStart: `${overrunLeft / units * 100}%`,
311
+ inlineSize: `${(right - overrunLeft) / units * 100}%`
312
+ },
313
+ children: [
314
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("rangeTimeline.overrun") }),
315
+ row.varianceLabel !== false && /* @__PURE__ */ jsx("span", { className: "ui-range-timeline-variance", children: row.varianceLabel ?? t("rangeTimeline.lateBy", { n: String(end - plan.end) }) })
316
+ ]
317
+ }
318
+ ),
319
+ dated && !plan && row.planNote != null && right > left && /* @__PURE__ */ jsx(
320
+ "span",
321
+ {
322
+ className: "ui-range-timeline-plan-note",
323
+ style: { insetInlineStart: `${right / units * 100}%` },
324
+ children: row.planNote
325
+ }
326
+ ),
327
+ early && right > left && right <= units && /* @__PURE__ */ jsx(
328
+ "span",
329
+ {
330
+ className: "ui-range-timeline-early",
331
+ style: { insetInlineStart: `${right / units * 100}%` },
332
+ children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("rangeTimeline.early") })
333
+ }
334
+ ),
335
+ dated && right > left && /* @__PURE__ */ jsxs(
203
336
  "div",
204
337
  {
205
338
  className: "ui-range-timeline-bar",
339
+ "data-muted": row.muted ? "true" : void 0,
340
+ "data-overdue": row.overdue ? "true" : void 0,
206
341
  style: {
207
342
  insetInlineStart: `${left / units * 100}%`,
208
- inlineSize: `${(right - left) / units * 100}%`
343
+ inlineSize: `${(right - left) / units * 100}%`,
344
+ ...row.color && {
345
+ "--range-timeline-bar-color": row.color,
346
+ "--range-timeline-bar-ink": inkOn(row.color)
347
+ }
209
348
  },
210
- children: onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
211
- (edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
212
- ).map((edge) => /* @__PURE__ */ jsx(
213
- Button,
214
- {
215
- variant: "ghost",
216
- size: "icon-xs",
217
- className: "ui-range-timeline-handle",
218
- "data-edge": edge,
219
- "aria-label": edge === "start" ? row.startLabel : row.endLabel,
220
- onPointerDown: (event) => {
221
- const width = track.current?.getBoundingClientRect().width ?? 0;
222
- if (!width || event.button !== 0) return;
223
- event.currentTarget.setPointerCapture(event.pointerId);
224
- drag.current = {
225
- id: row.id,
226
- edge,
227
- x: event.clientX,
228
- width,
229
- direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
230
- };
231
- },
232
- onPointerMove: (event) => {
233
- if (drag.current)
234
- setPreview({
235
- id: drag.current.id,
236
- edge: drag.current.edge,
237
- delta: deltaAt(event.clientX)
238
- });
239
- },
240
- onPointerUp: complete,
241
- onPointerCancel: cancel,
242
- onLostPointerCapture: cancel,
243
- onKeyDown: (event) => {
244
- if (event.key === "Escape") {
245
- cancel();
246
- return;
349
+ children: [
350
+ row.tooltip != null && /* @__PURE__ */ jsxs(Tooltip, { children: [
351
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
352
+ "span",
353
+ {
354
+ className: "ui-range-timeline-bar-hit ui-focus-ring",
355
+ tabIndex: 0,
356
+ "aria-labelledby": `${idBase}-label`
247
357
  }
248
- const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
249
- if (!step) return;
250
- event.preventDefault();
251
- const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
252
- const delta2 = step * direction;
253
- if (edge === "start" ? row.start + delta2 <= row.end : row.end + delta2 >= row.start)
254
- onRangeChange(row.id, edge, delta2);
358
+ ) }),
359
+ /* @__PURE__ */ jsx(TooltipContent, { children: row.tooltip })
360
+ ] }),
361
+ row.overdue && /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("rangeTimeline.overdue") }),
362
+ onRangeChange && (right - left) * Math.max(1, columns.length) >= units && ["start", "end"].filter(
363
+ (edge) => edge === "start" ? start >= 0 && start < units : end >= 0 && end < units
364
+ ).filter(
365
+ (edge) => row.editable === void 0 || row.editable === true ? true : row.editable === false ? false : row.editable[edge] !== false
366
+ ).map((edge) => /* @__PURE__ */ jsx(
367
+ Button,
368
+ {
369
+ variant: "ghost",
370
+ size: "icon-xs",
371
+ className: "ui-range-timeline-handle",
372
+ "data-edge": edge,
373
+ "aria-label": edge === "start" ? row.startLabel : row.endLabel,
374
+ onPointerDown: (event) => {
375
+ const width = track.current?.getBoundingClientRect().width ?? 0;
376
+ if (!width || event.button !== 0) return;
377
+ event.currentTarget.setPointerCapture(event.pointerId);
378
+ drag.current = {
379
+ id: row.id,
380
+ edge,
381
+ x: event.clientX,
382
+ width,
383
+ direction: getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
384
+ };
385
+ },
386
+ onPointerMove: (event) => {
387
+ if (drag.current)
388
+ setPreview({
389
+ id: drag.current.id,
390
+ edge: drag.current.edge,
391
+ delta: deltaAt(event.clientX)
392
+ });
393
+ },
394
+ onPointerUp: complete,
395
+ onPointerCancel: cancel,
396
+ onLostPointerCapture: cancel,
397
+ onKeyDown: (event) => {
398
+ if (event.key === "Escape") {
399
+ cancel();
400
+ return;
401
+ }
402
+ const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
403
+ if (!step) return;
404
+ event.preventDefault();
405
+ const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
406
+ const delta2 = step * direction;
407
+ if (edge === "start" ? rowStart + delta2 <= rowEnd : rowEnd + delta2 >= rowStart)
408
+ onRangeChange(row.id, edge, delta2);
409
+ },
410
+ children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
255
411
  },
256
- children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true" })
257
- },
258
- edge
259
- ))
412
+ edge
413
+ ))
414
+ ]
260
415
  }
261
416
  )
262
417
  ] })
@@ -274,7 +429,14 @@ const RangeTimeline = React.forwardRef(
274
429
  // Only a NON-default step scopes the unit-width knob, so a theme that narrows
275
430
  // --range-timeline-unit-width globally still owns the default timeline (gh#730).
276
431
  "data-density": density === "default" ? void 0 : density,
277
- "aria-label": label
432
+ "data-busy": busy ? "true" : void 0,
433
+ "aria-busy": busy || void 0,
434
+ "aria-label": label,
435
+ // Only once a width exists does it override the token, so a theme's width stays the default.
436
+ style: currentLabelWidth !== void 0 ? {
437
+ ...props.style,
438
+ "--range-timeline-label-width": `${currentLabelWidth}px`
439
+ } : props.style
278
440
  };
279
441
  if (!sticky) {
280
442
  return /* @__PURE__ */ jsx("section", { ...sectionProps, tabIndex: 0, children: /* @__PURE__ */ jsxs("div", { className: "ui-range-timeline-canvas", style: canvasStyle, children: [
@@ -55,6 +55,8 @@ export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command
55
55
  export type { CommandProps } from "./command.js";
56
56
  export { CommandPalette } from "./command-palette.js";
57
57
  export { EmojiPicker } from "./emoji-picker.js";
58
+ export { SortableList } from "./sortable-list.js";
59
+ export type { SortableListItemProp, SortableListProp, SortableListProps } from "./sortable-list.js";
58
60
  export type { EmojiPickerProp, EmojiPickerProps } from "./emoji-picker.js";
59
61
  export type { CommandPaletteGroup, CommandPaletteItem, CommandPaletteLabels, CommandPaletteProps, CommandPaletteSelectModifiers, } from "./command-palette.js";
60
62
  export { PasswordInput } from "./password-input.js";
@@ -54,6 +54,7 @@ import {
54
54
  } from "./command.js";
55
55
  import { CommandPalette } from "./command-palette.js";
56
56
  import { EmojiPicker } from "./emoji-picker.js";
57
+ import { SortableList } from "./sortable-list.js";
57
58
  import { PasswordInput } from "./password-input.js";
58
59
  import { PasswordStrength, usePasswordStrength } from "./password-strength.js";
59
60
  import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "./input-otp.js";
@@ -120,6 +121,7 @@ export {
120
121
  SelectTrigger,
121
122
  SelectValue,
122
123
  Slider,
124
+ SortableList,
123
125
  Switch,
124
126
  TagInput,
125
127
  Textarea,
@@ -1,9 +1,21 @@
1
1
  declare const DEFAULT_PASSWORD_RULES: readonly ["length", "upper", "lower", "number", "symbol"];
2
2
  export type PasswordRule = (typeof DEFAULT_PASSWORD_RULES)[number];
3
+ /**
4
+ * Every visible and screen-reader string, each optional (gh#1191). Unset entries come from the
5
+ * kit's own ja / en / vi catalogue for the active locale, so most apps pass nothing.
6
+ */
3
7
  export type PasswordStrengthLabels = {
4
- weak: string;
5
- fair: string;
6
- strong: string;
8
+ weak?: string;
9
+ fair?: string;
10
+ strong?: string;
11
+ /** Checklist line per rule. */
12
+ rules?: Partial<Record<PasswordRule, string>>;
13
+ /** Screen-reader prefix before a met rule (e.g. "Passed: "). */
14
+ passed?: string;
15
+ /** Screen-reader prefix before an unmet rule (e.g. "Not yet: "). */
16
+ failed?: string;
17
+ /** The polite live-region sentence, given the strength word. */
18
+ announcement?: (strength: string) => string;
7
19
  };
8
20
  export type PasswordStrengthProps = {
9
21
  value: string;
@@ -1,12 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Check, X } from "lucide-react";
4
+ import { useTranslation } from "../../i18n/use-translation.js";
4
5
  const DEFAULT_PASSWORD_RULES = ["length", "upper", "lower", "number", "symbol"];
5
- const DEFAULT_LABELS = {
6
- weak: "Weak",
7
- fair: "Fair",
8
- strong: "Strong"
9
- };
10
6
  function usePasswordStrength(value, rules = [...DEFAULT_PASSWORD_RULES]) {
11
7
  const uniqueRules = [...new Set(rules)];
12
8
  const checks = {
@@ -25,28 +21,33 @@ function scoreTone(score) {
25
21
  if (score <= 3) return "warning";
26
22
  return "success";
27
23
  }
28
- function scoreLabel(score, labels) {
29
- if (score <= 1) return labels.weak;
30
- if (score <= 3) return labels.fair;
31
- return labels.strong;
24
+ function scoreKey(score) {
25
+ if (score <= 1) return "weak";
26
+ if (score <= 3) return "fair";
27
+ return "strong";
32
28
  }
33
29
  function PasswordStrength({
34
30
  value,
35
31
  rules = [...DEFAULT_PASSWORD_RULES],
36
32
  showChecklist = true,
37
- labels = DEFAULT_LABELS
33
+ labels = {}
38
34
  }) {
35
+ const { t } = useTranslation();
39
36
  const normalizedRules = [...new Set(rules)];
40
37
  const { score, checks } = usePasswordStrength(value, normalizedRules);
41
38
  const tone = scoreTone(score);
42
39
  const segments = Array.from({ length: 5 });
40
+ const key = scoreKey(score);
41
+ const strength = labels[key] ?? t(`dataEntry.passwordStrength.${key}`);
42
+ const ruleLabel = (rule) => labels.rules?.[rule] ?? t(`dataEntry.passwordStrength.rules.${rule}`);
43
+ const announcement = labels.announcement ? labels.announcement(strength) : t("dataEntry.passwordStrength.announcement", { strength });
43
44
  return /* @__PURE__ */ jsxs("div", { className: "ui-password-strength", children: [
44
45
  /* @__PURE__ */ jsx(
45
46
  "div",
46
47
  {
47
48
  className: "ui-password-strength-track",
48
49
  role: "img",
49
- "aria-label": `Password strength ${score}/4`,
50
+ "aria-label": t("dataEntry.passwordStrength.meter", { score: String(score) }),
50
51
  children: segments.map((_, index) => {
51
52
  const filled = index < score + 1;
52
53
  return /* @__PURE__ */ jsx(
@@ -63,7 +64,7 @@ function PasswordStrength({
63
64
  }
64
65
  ),
65
66
  /* @__PURE__ */ jsxs("div", { className: "ui-password-strength-meta", children: [
66
- /* @__PURE__ */ jsx("span", { className: "ui-password-strength-label", children: scoreLabel(score, labels) }),
67
+ /* @__PURE__ */ jsx("span", { className: "ui-password-strength-label", children: strength }),
67
68
  /* @__PURE__ */ jsxs("span", { className: "ui-password-strength-score", "aria-hidden": "true", children: [
68
69
  score,
69
70
  "/4"
@@ -76,34 +77,15 @@ function PasswordStrength({
76
77
  "data-state": checks[rule] ? "passed" : "failed",
77
78
  children: [
78
79
  checks[rule] ? /* @__PURE__ */ jsx(Check, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(X, { "aria-hidden": "true" }),
79
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: checks[rule] ? "Passed: " : "Failed: " }),
80
- /* @__PURE__ */ jsx("span", { children: labelForRule(rule) })
80
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: checks[rule] ? labels.passed ?? t("dataEntry.passwordStrength.passed") : labels.failed ?? t("dataEntry.passwordStrength.failed") }),
81
+ /* @__PURE__ */ jsx("span", { children: ruleLabel(rule) })
81
82
  ]
82
83
  },
83
84
  rule
84
85
  )) }) : null,
85
- /* @__PURE__ */ jsxs("span", { className: "sr-only", "aria-live": "polite", children: [
86
- scoreLabel(score, labels),
87
- " password strength"
88
- ] })
86
+ /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", children: announcement })
89
87
  ] });
90
88
  }
91
- function labelForRule(rule) {
92
- switch (rule) {
93
- case "length":
94
- return "8+ characters";
95
- case "upper":
96
- return "Contains uppercase letter";
97
- case "lower":
98
- return "Contains lowercase letter";
99
- case "number":
100
- return "Contains number";
101
- case "symbol":
102
- return "Contains symbol";
103
- default:
104
- return rule;
105
- }
106
- }
107
89
  export {
108
90
  PasswordStrength,
109
91
  usePasswordStrength
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ import type { SortableListProp } from "../../props/components/data-entry.prop.js";
3
+ export type { SortableListItemProp, SortableListProp, SortableListProp as SortableListProps, } from "../../props/components/data-entry.prop.js";
4
+ /**
5
+ * SortableList (gh#1173) — reorder a flat list or a grid of tiles by pointer drag or by keyboard
6
+ * on each item's grip: Space/Enter lifts, the arrows (and Home/End) move, Space/Enter drops, Esc
7
+ * cancels and blur cancels. Every step is spoken through a polite live region ("2 / 3"). The order
8
+ * is the value: `onValueChange` receives the item keys in their new order once, on drop.
9
+ */
10
+ export declare const SortableList: React.ForwardRefExoticComponent<SortableListProp & React.RefAttributes<HTMLDivElement>>;