@eifi1/ui-kit 0.15.3 → 0.15.5

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 (122) hide show
  1. package/README.md +11 -10
  2. package/dist/chart.d.ts +50 -1
  3. package/dist/components/alert-banner.js +1 -1
  4. package/dist/components/alert-banner.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +5 -3
  6. package/dist/components/amount-input.js +6 -11
  7. package/dist/components/amount-input.js.map +1 -1
  8. package/dist/components/button-group.d.ts +2 -3
  9. package/dist/components/calculator.d.ts +2 -3
  10. package/dist/components/chart.d.ts +16 -1
  11. package/dist/components/chart.js +65 -37
  12. package/dist/components/chart.js.map +1 -1
  13. package/dist/components/checkbox.js +1 -1
  14. package/dist/components/checkbox.js.map +1 -1
  15. package/dist/components/chip.d.ts +8 -2
  16. package/dist/components/chip.js +12 -3
  17. package/dist/components/chip.js.map +1 -1
  18. package/dist/components/choice-card.js +1 -1
  19. package/dist/components/choice-card.js.map +1 -1
  20. package/dist/components/copy-button.d.ts +2 -3
  21. package/dist/components/error-boundary.js +1 -1
  22. package/dist/components/error-boundary.js.map +1 -1
  23. package/dist/components/facing-pair.d.ts +50 -0
  24. package/dist/components/file-button.d.ts +2 -3
  25. package/dist/components/file-dropzone.d.ts +2 -3
  26. package/dist/components/file-dropzone.js +1 -1
  27. package/dist/components/file-dropzone.js.map +1 -1
  28. package/dist/components/form-actions.d.ts +2 -3
  29. package/dist/components/form-actions.js +9 -1
  30. package/dist/components/form-actions.js.map +1 -1
  31. package/dist/components/money-field.d.ts +2 -3
  32. package/dist/components/number-field.d.ts +2 -3
  33. package/dist/components/number-input.d.ts +2 -3
  34. package/dist/components/number-input.js +17 -3
  35. package/dist/components/number-input.js.map +1 -1
  36. package/dist/components/numpad-sheet.d.ts +2 -3
  37. package/dist/components/series-chart.d.ts +78 -1
  38. package/dist/components/series-chart.js +74 -13
  39. package/dist/components/series-chart.js.map +1 -1
  40. package/dist/components/settings-fields.d.ts +2 -3
  41. package/dist/components/text-link.d.ts +2 -3
  42. package/dist/components/time-input.d.ts +2 -3
  43. package/dist/components/ui.d.ts +2 -3
  44. package/dist/components/ui.js +7 -7
  45. package/dist/components/ui.js.map +1 -1
  46. package/dist/feedback/feedback-attachment.d.ts +2 -82
  47. package/dist/feedback/feedback-attachment.js +271 -54
  48. package/dist/feedback/feedback-attachment.js.map +1 -1
  49. package/dist/feedback/feedback-dialog.d.ts +2 -105
  50. package/dist/feedback/feedback-dialog.js +56 -25
  51. package/dist/feedback/feedback-dialog.js.map +1 -1
  52. package/dist/feedback/feedback-inbox.d.ts +1 -1
  53. package/dist/feedback/feedback-thread.d.ts +2 -2
  54. package/dist/feedback-attachment-BYAHJMOr.d.ts +289 -0
  55. package/dist/feedback.d.ts +1 -2
  56. package/dist/hooks/use-file-drop.d.ts +2 -3
  57. package/dist/i18n/defaults.d.ts +2 -3
  58. package/dist/i18n/kit-labels.d.ts +2 -3
  59. package/dist/i18n/kit-labels.js +8 -2
  60. package/dist/i18n/kit-labels.js.map +1 -1
  61. package/dist/i18n/locales/de-CH-informal.d.ts +2 -3
  62. package/dist/i18n/locales/de-CH.d.ts +2 -3
  63. package/dist/i18n/locales/de-informal.d.ts +2 -3
  64. package/dist/i18n/locales/de.d.ts +2 -3
  65. package/dist/i18n/locales/de.js +5 -1
  66. package/dist/i18n/locales/de.js.map +1 -1
  67. package/dist/i18n/locales/es.d.ts +2 -3
  68. package/dist/i18n/locales/es.js +5 -1
  69. package/dist/i18n/locales/es.js.map +1 -1
  70. package/dist/i18n/locales/fr.d.ts +2 -3
  71. package/dist/i18n/locales/fr.js +5 -1
  72. package/dist/i18n/locales/fr.js.map +1 -1
  73. package/dist/i18n/locales/hu.d.ts +2 -3
  74. package/dist/i18n/locales/hu.js +5 -1
  75. package/dist/i18n/locales/hu.js.map +1 -1
  76. package/dist/i18n/locales/it.d.ts +2 -3
  77. package/dist/i18n/locales/it.js +5 -1
  78. package/dist/i18n/locales/it.js.map +1 -1
  79. package/dist/i18n/locales/zh.d.ts +2 -3
  80. package/dist/i18n/locales/zh.js +5 -1
  81. package/dist/i18n/locales/zh.js.map +1 -1
  82. package/dist/index.d.ts +4 -5
  83. package/dist/{kit-labels-BHfRBbHs.d.ts → kit-labels-BD2F7sOz.d.ts} +15 -5
  84. package/dist/lib/format.d.ts +15 -1
  85. package/dist/lib/format.js +16 -0
  86. package/dist/lib/format.js.map +1 -1
  87. package/dist/rhf/fields.d.ts +2 -3
  88. package/dist/rhf/form.d.ts +2 -3
  89. package/dist/rhf.d.ts +2 -3
  90. package/dist/shell/app-shell.d.ts +2 -3
  91. package/dist/shell/top-bar-brand.d.ts +2 -3
  92. package/dist/shell.d.ts +2 -3
  93. package/dist/theme/palette-derive.d.ts +3 -0
  94. package/dist/theme/palette-derive.js +7 -0
  95. package/dist/theme/palette-derive.js.map +1 -1
  96. package/dist/wizard/stepper-nav.d.ts +2 -3
  97. package/dist/wizard.d.ts +2 -3
  98. package/package.json +4 -2
  99. package/src/components/alert-banner.tsx +1 -1
  100. package/src/components/amount-input.tsx +16 -14
  101. package/src/components/chart.tsx +102 -41
  102. package/src/components/checkbox.tsx +1 -1
  103. package/src/components/chip.tsx +29 -4
  104. package/src/components/choice-card.tsx +1 -1
  105. package/src/components/error-boundary.tsx +1 -1
  106. package/src/components/file-dropzone.tsx +1 -1
  107. package/src/components/form-actions.tsx +9 -1
  108. package/src/components/number-input.tsx +22 -2
  109. package/src/components/series-chart.tsx +122 -20
  110. package/src/components/ui.tsx +7 -7
  111. package/src/feedback/feedback-attachment.tsx +441 -96
  112. package/src/feedback/feedback-dialog.tsx +147 -50
  113. package/src/i18n/kit-labels.tsx +19 -1
  114. package/src/i18n/locales/de.ts +4 -0
  115. package/src/i18n/locales/es.ts +4 -0
  116. package/src/i18n/locales/fr.ts +4 -0
  117. package/src/i18n/locales/hu.ts +4 -0
  118. package/src/i18n/locales/it.ts +4 -0
  119. package/src/i18n/locales/zh.ts +4 -0
  120. package/src/lib/format.ts +30 -0
  121. package/src/theme/palette-derive.ts +10 -0
  122. package/tokens.css +9 -1
@@ -1,4 +1,4 @@
1
- import { useId, useRef, useState } from "react";
1
+ import { useId, useLayoutEffect, useRef, useState } from "react";
2
2
  import type { KeyboardEvent, ReactNode } from "react";
3
3
  import { CalculatorButton, type CalculatorButtonLabels } from "./calculator";
4
4
  import { NumberPadSheet, type NumberPadSheetLabels } from "./numpad-sheet";
@@ -191,6 +191,21 @@ export function NumberInput({
191
191
  const asDisplay = isMobile && variant === "display";
192
192
  const showCalc = calculator && !disabled && !isMobile;
193
193
  const [focused, setFocused] = useState(false);
194
+ // The unit and the calculator's measured width, reserved as the field's end padding.
195
+ const endRef = useRef<HTMLDivElement>(null);
196
+ const [endWidth, setEndWidth] = useState<number | null>(null);
197
+ const hasEnd = showCalc || suffix !== undefined;
198
+ useLayoutEffect(() => {
199
+ const el = endRef.current;
200
+ if (!hasEnd || !el || typeof ResizeObserver === "undefined") return;
201
+ // The first callback fires on `observe`; no layout (jsdom) keeps the class fallback.
202
+ const observer = new ResizeObserver(() => {
203
+ const width = el.getBoundingClientRect().width;
204
+ setEndWidth(width > 0 ? Math.ceil(width) : null);
205
+ });
206
+ observer.observe(el);
207
+ return () => observer.disconnect();
208
+ }, [hasEnd]);
194
209
  // On mobile, focusing opens the numpad bottom sheet instead of the native
195
210
  // keyboard (#334); the ref lets its "Done" blur → commit + close.
196
211
  const showNumpad = isMobile && !disabled && focused;
@@ -272,12 +287,17 @@ export function NumberInput({
272
287
  showCalc && suffix !== undefined ? "pe-16" : showCalc ? "pe-9" : suffix !== undefined ? "pe-8" : undefined,
273
288
  invalid && FIELD_INVALID,
274
289
  )}
290
+ // The classes above are the fallback; once the end controls are measured the
291
+ // field reserves exactly their width (+4px). A fixed pe-8 for any unit left a
292
+ // "%" in a ~78px table cell 32px of padding and the digits ~32px of room
293
+ // (keksdose live #367); a long unit ("km/h") got too little.
294
+ style={hasEnd && endWidth != null ? { paddingInlineEnd: endWidth + 4 } : undefined}
275
295
  />
276
296
  {(showCalc || suffix !== undefined) && (
277
297
  // One flex track for both, so the unit and the calculator sit side by side
278
298
  // instead of stacking on the same corner — AmountInput's arrangement, which
279
299
  // has carried a chip and a calculator together since #430.
280
- <div className="absolute inset-y-0 end-0 flex items-center">
300
+ <div ref={endRef} className="absolute inset-y-0 end-0 flex items-center">
281
301
  {showCalc && (
282
302
  <CalculatorButton
283
303
  value={value}
@@ -68,7 +68,13 @@ import { autoAxisBudget, axisUnit, budgetedAxes, type SeriesChartAxisBudget } fr
68
68
  // The rule stays internal, like the ticks; the one type a public prop names is re-exported.
69
69
  export type { SeriesChartAxisBudget } from "./series-chart-budget";
70
70
  import { paletteFor } from "../theme/chart-palette";
71
- import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
71
+ import {
72
+ useKitChartTooltipPlacement,
73
+ useKitLabels,
74
+ useKitLocale,
75
+ type ChartTooltipPlacement,
76
+ } from "../i18n/kit-labels";
77
+ import { useMediaQuery } from "../hooks/use-media-query";
72
78
  import { cn } from "../lib/cn";
73
79
 
74
80
  export interface SeriesChartSeries {
@@ -356,8 +362,49 @@ export interface SeriesChartX {
356
362
  integerTicks?: boolean;
357
363
  }
358
364
 
365
+ /** Where the tooltip goes — see {@link SeriesChartTooltip.placement}. */
366
+ export type SeriesChartTooltipPlacement = ChartTooltipPlacement;
367
+
368
+ /** Below Tailwind's `sm` — where `"auto"` takes the tooltip off the plot. Narrower than
369
+ * the kit's `PHONE_QUERY` (767 px) on purpose: a tablet's plot is wide enough that the
370
+ * box beside the finger leaves the data in view; a phone's is not. */
371
+ const TOOLTIP_READOUT_QUERY = "(max-width: 639px)";
372
+
373
+ /**
374
+ * The placement a chart actually uses: its own, else the provider's, else `"cursor"`;
375
+ * `"auto"` is `"above"` on a phone (`phone`: below `sm`) and `"cursor"` from `sm` up.
376
+ */
377
+ export function resolveTooltipPlacement(
378
+ own: SeriesChartTooltipPlacement | undefined,
379
+ fromProvider: SeriesChartTooltipPlacement | undefined,
380
+ phone: boolean,
381
+ ): "cursor" | "above" | "below" {
382
+ const wanted = own ?? fromProvider ?? "cursor";
383
+ return wanted === "auto" ? (phone ? "above" : "cursor") : wanted;
384
+ }
385
+
359
386
  /** Room for the tooltip beyond the chart — see {@link SeriesChartProps.tooltip}. */
360
387
  export interface SeriesChartTooltip {
388
+ /**
389
+ * Where the values under the pointer are shown (keksdose #359: on a phone the box
390
+ * sat under the finger, on the very data it was reporting).
391
+ * - `"cursor"` (the default): the floating box that follows the pointer.
392
+ * - `"above"` / `"below"`: a READOUT row — the same heading and values, flat — in
393
+ * room reserved over or under the plot from the first render, so nothing jumps on
394
+ * the first tap. It follows hover, a tap and the keyboard's stops; the cursor line
395
+ * and the active dot stay on the plot, and no box floats. Idle, it lists the series
396
+ * with "—" under the x axis' title. The row is taken out of the chart's `height`
397
+ * (the plot gets shorter, the page around it does not move).
398
+ * - `"auto"`: `"above"` below Tailwind's `sm` (640 px), `"cursor"` from there up.
399
+ *
400
+ * Default: the provider's `chartTooltipPlacement`, else `"cursor"`.
401
+ *
402
+ * The row is deliberately NOT an `aria-live` region: scrubbing a line announces a
403
+ * value per pixel of travel. A keyboard reader already hears each stop's name
404
+ * ("period — series: value") from the stop itself, and the row stays in the
405
+ * document for a screen reader to read on its way past.
406
+ */
407
+ placement?: SeriesChartTooltipPlacement;
361
408
  /** Let the tooltip run past the chart's own box on that axis instead of recharts
362
409
  * clamping it inside. Default: `{ x: true }` when `boundary` is set, else neither. */
363
410
  allowEscapeViewBox?: { x?: boolean; y?: boolean };
@@ -1173,6 +1220,10 @@ function SeriesPlot({
1173
1220
  // Up here, above the empty state's early return, like every hook.
1174
1221
  const [keyStop, setKeyStop] = useState(0);
1175
1222
  const [keyFocus, setKeyFocus] = useState<number | undefined>(undefined);
1223
+ // Where the tooltip goes, and — off the plot — the row it is portalled into.
1224
+ const phone = useMediaQuery(TOOLTIP_READOUT_QUERY, false);
1225
+ const placement = resolveTooltipPlacement(tooltip?.placement, useKitChartTooltipPlacement(), phone);
1226
+ const [readout, setReadout] = useState<HTMLDivElement | null>(null);
1176
1227
 
1177
1228
  // The chart's own width, for the automatic axis budget. Its box, not the plot's: the
1178
1229
  // box does not change when an axis is hidden, so the rule cannot oscillate. Nothing
@@ -1495,13 +1546,21 @@ function SeriesPlot({
1495
1546
  animationEasing: "ease-out" as const,
1496
1547
  };
1497
1548
 
1498
- return (
1549
+ const inRow = placement !== "cursor";
1550
+ const chart = (
1499
1551
  <ChartContainer
1500
1552
  ref={setBox}
1501
1553
  data-axis-budget={budgeted.length ? budgetKey.replace(/\n/g, " ") : undefined}
1502
1554
  config={config}
1503
- className={cn("w-full", heightClass, className, onPointClick && "cursor-pointer")}
1504
- style={heightStyle}
1555
+ className={cn(
1556
+ "w-full",
1557
+ // With a readout the chart's height is the whole block's, and the plot takes
1558
+ // what the row leaves.
1559
+ inRow ? "min-h-0 flex-1" : heightClass,
1560
+ className,
1561
+ onPointClick && "cursor-pointer",
1562
+ )}
1563
+ style={inRow ? undefined : heightStyle}
1505
1564
  >
1506
1565
  <ComposedChart
1507
1566
  data={plotted as Record<string, unknown>[]}
@@ -1607,23 +1666,45 @@ function SeriesPlot({
1607
1666
  </YAxis>
1608
1667
  );
1609
1668
  })}
1610
- <ChartTooltip
1611
- {...(tooltip?.allowEscapeViewBox || tooltip?.boundary
1612
- ? { allowEscapeViewBox: tooltip.allowEscapeViewBox ?? { x: true } }
1613
- : {})}
1614
- // The content shifts itself 12 px off the cursor (or flips) against the
1615
- // boundary; recharts' own offset on top would double it.
1616
- {...(tooltip?.boundary ? { offset: 0 } : {})}
1617
- // The keyboard's stop shows its tooltip as the pointer's would.
1618
- defaultIndex={focusedStop?.index}
1619
- content={
1620
- <ChartTooltipContent
1621
- labelFormatter={(value) => xLabel(Number(value))}
1622
- valueFormatter={valueFormat ?? number}
1623
- boundaryRef={tooltip?.boundary}
1669
+ {inRow ? (
1670
+ // Not before the row exists: without its own portal recharts would float this
1671
+ // content over the plot for the first frame.
1672
+ readout && (
1673
+ <ChartTooltip
1674
+ portal={readout}
1675
+ // recharts hides an inactive tooltip; the row shows its idle state instead.
1676
+ wrapperStyle={{ visibility: "visible" }}
1677
+ defaultIndex={focusedStop?.index}
1678
+ content={
1679
+ <ChartTooltipContent
1680
+ variant="readout"
1681
+ idleKeys={series.map((entry) => entry.key)}
1682
+ idleLabel={x.title || undefined}
1683
+ labelFormatter={(value) => xLabel(Number(value))}
1684
+ valueFormatter={valueFormat ?? number}
1685
+ />
1686
+ }
1624
1687
  />
1625
- }
1626
- />
1688
+ )
1689
+ ) : (
1690
+ <ChartTooltip
1691
+ {...(tooltip?.allowEscapeViewBox || tooltip?.boundary
1692
+ ? { allowEscapeViewBox: tooltip.allowEscapeViewBox ?? { x: true } }
1693
+ : {})}
1694
+ // The content shifts itself 12 px off the cursor (or flips) against the
1695
+ // boundary; recharts' own offset on top would double it.
1696
+ {...(tooltip?.boundary ? { offset: 0 } : {})}
1697
+ // The keyboard's stop shows its tooltip as the pointer's would.
1698
+ defaultIndex={focusedStop?.index}
1699
+ content={
1700
+ <ChartTooltipContent
1701
+ labelFormatter={(value) => xLabel(Number(value))}
1702
+ valueFormatter={valueFormat ?? number}
1703
+ boundaryRef={tooltip?.boundary}
1704
+ />
1705
+ }
1706
+ />
1707
+ )}
1627
1708
  {series.map((entry) => {
1628
1709
  const type = entry.type ?? "line";
1629
1710
  const yAxisId = entry.axis ?? DEFAULT_Y_AXIS;
@@ -1787,6 +1868,27 @@ function SeriesPlot({
1787
1868
  </ComposedChart>
1788
1869
  </ChartContainer>
1789
1870
  );
1871
+ if (!inRow) return chart;
1872
+ // The row's own box, reserved from the first render: its idle state has the cells the
1873
+ // active one will, so the first tap fills it in rather than pushing the plot down.
1874
+ const row = (
1875
+ <div
1876
+ ref={setReadout}
1877
+ data-tooltip-readout={placement}
1878
+ className={cn(
1879
+ "shrink-0",
1880
+ // Above, the heading line shares the top corner with the zoom's reset button.
1881
+ placement === "above" ? "pb-2 [&_[data-readout-heading]]:pe-32" : "pt-2",
1882
+ )}
1883
+ />
1884
+ );
1885
+ return (
1886
+ <div className={cn("flex w-full flex-col", heightClass)} style={heightStyle}>
1887
+ {placement === "above" && row}
1888
+ {chart}
1889
+ {placement === "below" && row}
1890
+ </div>
1891
+ );
1790
1892
  }
1791
1893
 
1792
1894
  const ZoomablePlot = withChartZoom(SeriesPlot);
@@ -46,7 +46,7 @@ const buttonVariantClasses: Record<ButtonVariant, string> = {
46
46
  ghost:
47
47
  "bg-transparent text-[var(--text-primary)] hover:bg-[var(--bg-surface-2)] focus:ring-[var(--brand)]",
48
48
  danger:
49
- "bg-[var(--danger)] text-[var(--danger-contrast)] hover:bg-[var(--danger-hover)] focus:ring-[var(--danger-border)]",
49
+ "bg-[var(--danger)] text-[var(--danger-contrast)] hover:bg-[var(--danger-hover)] focus:ring-[var(--danger-border-strong)]",
50
50
  brand:
51
51
  "bg-[var(--brand)] text-[var(--brand-contrast)] hover:bg-[var(--brand-hover)] focus:ring-[var(--brand)]",
52
52
  // A text link that is still a `<button>` — keksdose's six hand-rolled
@@ -92,7 +92,7 @@ const BUTTON_TONES: Record<Exclude<ButtonTone, "default">, string> = {
92
92
  muted:
93
93
  "text-[var(--text-secondary)] hover:text-[var(--text-primary)] disabled:hover:text-[var(--text-secondary)]",
94
94
  danger:
95
- "text-[var(--text-secondary)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)] disabled:hover:text-[var(--text-secondary)]",
95
+ "text-[var(--text-secondary)] hover:text-[var(--danger)] focus:ring-[var(--danger-border-strong)] disabled:hover:text-[var(--text-secondary)]",
96
96
  };
97
97
 
98
98
  /** Only the two transparent variants take every tone; see {@link ButtonTone}. */
@@ -103,9 +103,9 @@ const TONED_VARIANTS = new Set<ButtonVariant>(["link", "ghost"]);
103
103
  // the neutral ones survives the merge.
104
104
  const BUTTON_BOXED_DANGER: Partial<Record<ButtonVariant, string>> = {
105
105
  secondary:
106
- "border-[var(--danger-border)] bg-transparent text-[var(--danger)] hover:bg-[var(--danger-bg)] focus:ring-[var(--danger-border)]",
106
+ "border-[var(--danger-border)] bg-transparent text-[var(--danger)] hover:bg-[var(--danger-bg)] focus:ring-[var(--danger-border-strong)]",
107
107
  primary:
108
- "border-[var(--danger-border)] bg-[var(--danger-bg)] text-[var(--danger)] hover:border-[var(--danger)] hover:bg-[var(--danger)] hover:text-[var(--danger-contrast)] focus:ring-[var(--danger-border)] disabled:hover:border-[var(--danger-border)] disabled:hover:bg-[var(--danger-bg)] disabled:hover:text-[var(--danger)]",
108
+ "border-[var(--danger-border)] bg-[var(--danger-bg)] text-[var(--danger)] hover:border-[var(--danger)] hover:bg-[var(--danger)] hover:text-[var(--danger-contrast)] focus:ring-[var(--danger-border-strong)] disabled:hover:border-[var(--danger-border)] disabled:hover:bg-[var(--danger-bg)] disabled:hover:text-[var(--danger)]",
109
109
  };
110
110
 
111
111
  // `pressed` on a `link`: the brand colour and a heavier weight, which is exactly what
@@ -543,8 +543,8 @@ type ColouredTone = "danger" | "warning" | "info" | "success" | "custom";
543
543
  const ICON_BUTTON_TONES: Record<ColouredTone, { quiet: string; toned: string }> = {
544
544
  danger: {
545
545
  quiet:
546
- "text-[var(--text-placeholder)] hover:bg-[var(--danger-bg)] hover:text-[var(--danger)] focus:ring-[var(--danger-border)]",
547
- toned: "text-[var(--danger)] hover:bg-[var(--danger-bg)] focus:ring-[var(--danger-border)]",
546
+ "text-[var(--text-placeholder)] hover:bg-[var(--danger-bg)] hover:text-[var(--danger)] focus:ring-[var(--danger-border-strong)]",
547
+ toned: "text-[var(--danger)] hover:bg-[var(--danger-bg)] focus:ring-[var(--danger-border-strong)]",
548
548
  },
549
549
  warning: {
550
550
  quiet:
@@ -1250,7 +1250,7 @@ export const FIELD_FLOATING_PAD = "pt-4 pb-1";
1250
1250
  // stays exactly the size of a valid one and nothing beside it moves when the value
1251
1251
  // arrives.
1252
1252
  export const FIELD_INVALID =
1253
- "border-[var(--danger-border)] ring-1 ring-[var(--danger-border)] focus:border-[var(--danger)] focus:ring-[var(--danger)]";
1253
+ "border-[var(--danger-border-strong)] ring-1 ring-[var(--danger-border-strong)] focus:border-[var(--danger)] focus:ring-[var(--danger)]";
1254
1254
 
1255
1255
  export const FLOATING_INPUT_CLASS = cn(FIELD_BASE, FIELD_FLOATING_PAD, "peer placeholder:text-transparent");
1256
1256