@repraze/kernelkit 0.1.0-alpha.4 → 0.1.0-alpha.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 (115) hide show
  1. package/dist/components/alert-dialog.d.ts.map +1 -1
  2. package/dist/components/alert-dialog.js +4 -1
  3. package/dist/components/alert-dialog.js.map +1 -1
  4. package/dist/components/alert.js +3 -3
  5. package/dist/components/alert.js.map +1 -1
  6. package/dist/components/badge.js +1 -1
  7. package/dist/components/badge.js.map +1 -1
  8. package/dist/components/button.js +1 -1
  9. package/dist/components/button.js.map +1 -1
  10. package/dist/components/calendar.d.ts +8 -1
  11. package/dist/components/calendar.d.ts.map +1 -1
  12. package/dist/components/calendar.js +33 -11
  13. package/dist/components/calendar.js.map +1 -1
  14. package/dist/components/callout.js +2 -2
  15. package/dist/components/callout.js.map +1 -1
  16. package/dist/components/combobox.js +1 -1
  17. package/dist/components/combobox.js.map +1 -1
  18. package/dist/components/date-picker.d.ts +11 -5
  19. package/dist/components/date-picker.d.ts.map +1 -1
  20. package/dist/components/date-picker.js +29 -7
  21. package/dist/components/date-picker.js.map +1 -1
  22. package/dist/components/description-list.d.ts +26 -0
  23. package/dist/components/description-list.d.ts.map +1 -0
  24. package/dist/components/description-list.js +59 -0
  25. package/dist/components/description-list.js.map +1 -0
  26. package/dist/components/locale.d.ts +19 -0
  27. package/dist/components/locale.d.ts.map +1 -0
  28. package/dist/components/locale.js +54 -0
  29. package/dist/components/locale.js.map +1 -0
  30. package/dist/components/popover.d.ts +1 -1
  31. package/dist/components/popover.d.ts.map +1 -1
  32. package/dist/components/popover.js +2 -2
  33. package/dist/components/popover.js.map +1 -1
  34. package/dist/components/progress.d.ts +19 -6
  35. package/dist/components/progress.d.ts.map +1 -1
  36. package/dist/components/progress.js +18 -3
  37. package/dist/components/progress.js.map +1 -1
  38. package/dist/components/search-trigger.d.ts +4 -1
  39. package/dist/components/search-trigger.d.ts.map +1 -1
  40. package/dist/components/search-trigger.js +5 -2
  41. package/dist/components/search-trigger.js.map +1 -1
  42. package/dist/components/shimmer.d.ts +13 -0
  43. package/dist/components/shimmer.d.ts.map +1 -0
  44. package/dist/components/shimmer.js +21 -0
  45. package/dist/components/shimmer.js.map +1 -0
  46. package/dist/components/spinner.d.ts +28 -6
  47. package/dist/components/spinner.d.ts.map +1 -1
  48. package/dist/components/spinner.js +66 -14
  49. package/dist/components/spinner.js.map +1 -1
  50. package/dist/components/stepper.d.ts +64 -0
  51. package/dist/components/stepper.d.ts.map +1 -0
  52. package/dist/components/stepper.js +167 -0
  53. package/dist/components/stepper.js.map +1 -0
  54. package/dist/components/tag-input.d.ts +47 -0
  55. package/dist/components/tag-input.d.ts.map +1 -0
  56. package/dist/components/tag-input.js +211 -0
  57. package/dist/components/tag-input.js.map +1 -0
  58. package/dist/components/time-input.d.ts +75 -0
  59. package/dist/components/time-input.d.ts.map +1 -0
  60. package/dist/components/time-input.js +341 -0
  61. package/dist/components/time-input.js.map +1 -0
  62. package/dist/components/time-select.d.ts +49 -0
  63. package/dist/components/time-select.d.ts.map +1 -0
  64. package/dist/components/time-select.js +178 -0
  65. package/dist/components/time-select.js.map +1 -0
  66. package/dist/components/timeline.d.ts +41 -0
  67. package/dist/components/timeline.d.ts.map +1 -0
  68. package/dist/components/timeline.js +83 -0
  69. package/dist/components/timeline.js.map +1 -0
  70. package/dist/components/toast.d.ts.map +1 -1
  71. package/dist/components/toast.js +4 -2
  72. package/dist/components/toast.js.map +1 -1
  73. package/dist/components/tree.d.ts +87 -0
  74. package/dist/components/tree.d.ts.map +1 -0
  75. package/dist/components/tree.js +516 -0
  76. package/dist/components/tree.js.map +1 -0
  77. package/dist/hooks/use-time-format.d.ts +23 -0
  78. package/dist/hooks/use-time-format.d.ts.map +1 -0
  79. package/dist/hooks/use-time-format.js +52 -0
  80. package/dist/hooks/use-time-format.js.map +1 -0
  81. package/dist/lib/color.d.ts +5 -1
  82. package/dist/lib/color.d.ts.map +1 -1
  83. package/dist/lib/color.js +14 -2
  84. package/dist/lib/color.js.map +1 -1
  85. package/dist/lib/recipes.d.ts +5 -4
  86. package/dist/lib/recipes.d.ts.map +1 -1
  87. package/dist/lib/recipes.js +7 -5
  88. package/dist/lib/recipes.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/components/alert-dialog.tsx +4 -1
  91. package/src/components/alert.tsx +3 -3
  92. package/src/components/badge.tsx +1 -1
  93. package/src/components/button.tsx +1 -1
  94. package/src/components/calendar.tsx +48 -4
  95. package/src/components/callout.tsx +2 -2
  96. package/src/components/combobox.tsx +1 -1
  97. package/src/components/date-picker.tsx +43 -9
  98. package/src/components/description-list.tsx +107 -0
  99. package/src/components/locale.tsx +67 -0
  100. package/src/components/popover.tsx +3 -1
  101. package/src/components/progress.tsx +70 -4
  102. package/src/components/search-trigger.tsx +8 -2
  103. package/src/components/shimmer.tsx +36 -0
  104. package/src/components/spinner.tsx +126 -23
  105. package/src/components/stepper.tsx +346 -0
  106. package/src/components/tag-input.tsx +345 -0
  107. package/src/components/time-input.tsx +578 -0
  108. package/src/components/time-select.tsx +291 -0
  109. package/src/components/timeline.tsx +190 -0
  110. package/src/components/toast.tsx +4 -4
  111. package/src/components/tree.tsx +832 -0
  112. package/src/hooks/use-time-format.ts +72 -0
  113. package/src/lib/color.ts +19 -2
  114. package/src/lib/recipes.ts +7 -5
  115. package/src/styles/kernelkit.css +56 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-time-format.js","sourceRoot":"","sources":["../../src/hooks/use-time-format.ts"],"names":[],"mappings":"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"}
@@ -6,9 +6,13 @@
6
6
  * - `--c-tint` opaque wash behind foreground text
7
7
  * - `--c-fill` / `--c-fill-fg` vivid solid surface and its text (TermiColor "fills are the brand":
8
8
  * in light mode the text tier is darkened for contrast, the fill tier stays vivid)
9
+ * - `--c-soft-text` text on a wash of its own color (soft variants, soft status surfaces): `--c` mixed 25%
10
+ * toward the foreground with its chroma restored, which keeps hue and saturation and passes WCAG AA
11
+ * 4.5:1 for every color, in light, dark and both hc modes, on background, card and popover (plain `--c`
12
+ * on its own wash fell to ~3.6)
9
13
  *
10
14
  * Spread `colorVariants` into a cva `variants.color` map, then write variants against the vars
11
- * (`bg-(--c) text-(--c-fg)`, `bg-(--c)/12 text-(--c)`, …).
15
+ * (`bg-(--c) text-(--c-fg)`, `bg-(--c)/12 text-(--c-soft-text)`, …).
12
16
  */
13
17
  export declare const STATUS_COLORS: readonly ["danger", "warning", "success", "info"];
14
18
  export declare const HUES: readonly ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink"];
@@ -1 +1 @@
1
- {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/lib/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,aAAa,mDAAoD,CAAC;AAC/E,eAAO,MAAM,IAAI,iFAAkF,CAAC;AACpG,eAAO,MAAM,MAAM,+IAA6D,CAAC;AAEjF,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAG5C,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAmB/C,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,aAAa,yIAAwC,CAAC;AAEnE,uEAAuE;AACvE,eAAO,MAAM,aAAa,wDAAyD,CAAC;AACpF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,yCAAyC;AACzC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAiBvE,uDAAuD;AACvD,wBAAgB,WAAW,CACvB,OAAO,EAAE,WAAW,GAAG,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAC1D,KAAK,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,EAC/B,QAAQ,GAAE,WAAqB,GAChC;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAC,CAItC"}
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/lib/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,aAAa,mDAAoD,CAAC;AAC/E,eAAO,MAAM,IAAI,iFAAkF,CAAC;AACpG,eAAO,MAAM,MAAM,+IAA6D,CAAC;AAEjF,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAiC5C,eAAO,MAAM,aAAa,EAErB,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAE3B,oFAAoF;AACpF,eAAO,MAAM,aAAa,yIAAwC,CAAC;AAEnE,uEAAuE;AACvE,eAAO,MAAM,aAAa,wDAAyD,CAAC;AACpF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,yCAAyC;AACzC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAiBvE,uDAAuD;AACvD,wBAAgB,WAAW,CACvB,OAAO,EAAE,WAAW,GAAG,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAC1D,KAAK,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,EAC/B,QAAQ,GAAE,WAAqB,GAChC;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAC,CAItC"}
package/dist/lib/color.js CHANGED
@@ -6,15 +6,26 @@
6
6
  * - `--c-tint` opaque wash behind foreground text
7
7
  * - `--c-fill` / `--c-fill-fg` vivid solid surface and its text (TermiColor "fills are the brand":
8
8
  * in light mode the text tier is darkened for contrast, the fill tier stays vivid)
9
+ * - `--c-soft-text` text on a wash of its own color (soft variants, soft status surfaces): `--c` mixed 25%
10
+ * toward the foreground with its chroma restored, which keeps hue and saturation and passes WCAG AA
11
+ * 4.5:1 for every color, in light, dark and both hc modes, on background, card and popover (plain `--c`
12
+ * on its own wash fell to ~3.6)
9
13
  *
10
14
  * Spread `colorVariants` into a cva `variants.color` map, then write variants against the vars
11
- * (`bg-(--c) text-(--c-fg)`, `bg-(--c)/12 text-(--c)`, …).
15
+ * (`bg-(--c) text-(--c-fg)`, `bg-(--c)/12 text-(--c-soft-text)`, …).
12
16
  */
13
17
  export const STATUS_COLORS = ["danger", "warning", "success", "info"];
14
18
  export const HUES = ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink"];
15
19
  export const COLORS = ["neutral", "primary", ...STATUS_COLORS, ...HUES];
20
+ /**
21
+ * Derived from `--c` on the same element, so it is set alongside every color (a :root rule would see no `--c`).
22
+ * The mix toward the (neutral) foreground sets the lightness for contrast but also scales chroma by 0.75, so
23
+ * where relative color syntax is supported the chroma is multiplied back by 1/0.75: same lightness and hue,
24
+ * the original saturation (≥ 5.08:1 everywhere, vs ≥ 5.68 for the plain mix kept as the fallback).
25
+ */
26
+ const SOFT_TEXT = "[--c-soft-text:color-mix(in_oklab,var(--c)_75%,var(--foreground))] supports-[color:oklch(from_red_l_c_h)]:[--c-soft-text:oklch(from_color-mix(in_oklab,var(--c)_75%,var(--foreground))_l_calc(c*1.333)_h)]";
16
27
  // Literal strings on purpose: Tailwind only generates classes it can read in the source.
17
- export const colorVariants = {
28
+ const baseColorVariants = {
18
29
  neutral: "[--c:var(--foreground)] [--c-fg:var(--background)] [--c-tint:var(--secondary)] [--c-fill:var(--foreground)] [--c-fill-fg:var(--background)]",
19
30
  primary: "[--c:var(--primary-text)] [--c-fg:var(--tc-on-accent)] [--c-tint:var(--accent)] [--c-fill:var(--primary)] [--c-fill-fg:var(--primary-foreground)]",
20
31
  danger: "[--c:var(--danger)] [--c-fg:var(--danger-foreground)] [--c-tint:var(--danger-tint)] [--c-fill:var(--tc-red-fill)] [--c-fill-fg:var(--tc-red-on-fill)]",
@@ -30,6 +41,7 @@ export const colorVariants = {
30
41
  purple: "[--c:var(--tc-purple)] [--c-fg:var(--tc-purple-on)] [--c-tint:var(--tc-purple-tint)] [--c-fill:var(--tc-purple-fill)] [--c-fill-fg:var(--tc-purple-on-fill)]",
31
42
  pink: "[--c:var(--tc-pink)] [--c-fg:var(--tc-pink-on)] [--c-tint:var(--tc-pink-tint)] [--c-fill:var(--tc-pink-fill)] [--c-fill-fg:var(--tc-pink-on-fill)]",
32
43
  };
44
+ export const colorVariants = Object.fromEntries(Object.entries(baseColorVariants).map(([color, vars]) => [color, `${vars} ${SOFT_TEXT}`]));
33
45
  /** Every color except neutral: the ones whose outline/ghost variants are tinted. */
34
46
  export const TINTED_COLORS = COLORS.filter((c) => c !== "neutral");
35
47
  /** Fill styles shared by Button, Badge, Toggle… (GUIDELINES.md §2). */
@@ -1 +1 @@
1
- {"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/lib/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAU,CAAC;AAC/E,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AACpG,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,aAAa,EAAE,GAAG,IAAI,CAAU,CAAC;AAIjF,yFAAyF;AACzF,MAAM,CAAC,MAAM,aAAa,GAA0B;IAChD,OAAO,EACH,6IAA6I;IACjJ,OAAO,EACH,mJAAmJ;IACvJ,MAAM,EAAE,uJAAuJ;IAC/J,OAAO,EACH,gKAAgK;IACpK,OAAO,EACH,8JAA8J;IAClK,IAAI,EAAE,mJAAmJ;IACzJ,GAAG,EAAE,+IAA+I;IACpJ,MAAM,EAAE,8JAA8J;IACtK,MAAM,EAAE,8JAA8J;IACtK,KAAK,EAAE,yJAAyJ;IAChK,IAAI,EAAE,oJAAoJ;IAC1J,IAAI,EAAE,oJAAoJ;IAC1J,MAAM,EAAE,8JAA8J;IACtK,IAAI,EAAE,oJAAoJ;CAC7J,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;AAEnE,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAMpF,MAAM,OAAO,GAAoE;IAC7E,OAAO,EAAE,EAAC,OAAO,EAAE,OAAO,EAAC;IAC3B,SAAS,EAAE,EAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC;IAC/C,WAAW,EAAE,EAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAC;CAClD,CAAC;AAEF,oGAAoG;AACpG,MAAM,aAAa,GAA+B;IAC9C,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,SAAS;CACnB,CAAC;AAEF,uDAAuD;AACvD,MAAM,UAAU,WAAW,CACvB,OAA0D,EAC1D,KAA+B,EAC/B,WAAwB,OAAO;IAE/B,MAAM,KAAK,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAA2B,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/F,MAAM,CAAC,GAAG,KAAK,EAAE,OAAO,IAAK,OAA0C,IAAI,QAAQ,CAAC;IACpF,OAAO,EAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK,EAAE,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC,EAAC,CAAC;AAC1E,CAAC"}
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/lib/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAU,CAAC;AAC/E,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AACpG,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,aAAa,EAAE,GAAG,IAAI,CAAU,CAAC;AAIjF;;;;;GAKG;AACH,MAAM,SAAS,GACX,4MAA4M,CAAC;AAEjN,yFAAyF;AACzF,MAAM,iBAAiB,GAA0B;IAC7C,OAAO,EACH,6IAA6I;IACjJ,OAAO,EACH,mJAAmJ;IACvJ,MAAM,EAAE,uJAAuJ;IAC/J,OAAO,EACH,gKAAgK;IACpK,OAAO,EACH,8JAA8J;IAClK,IAAI,EAAE,mJAAmJ;IACzJ,GAAG,EAAE,+IAA+I;IACpJ,MAAM,EAAE,8JAA8J;IACtK,MAAM,EAAE,8JAA8J;IACtK,KAAK,EAAE,yJAAyJ;IAChK,IAAI,EAAE,oJAAoJ;IAC1J,IAAI,EAAE,oJAAoJ;IAC1J,MAAM,EAAE,8JAA8J;IACtK,IAAI,EAAE,oJAAoJ;CAC7J,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAC3C,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CACnE,CAAC;AAE3B,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;AAEnE,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAMpF,MAAM,OAAO,GAAoE;IAC7E,OAAO,EAAE,EAAC,OAAO,EAAE,OAAO,EAAC;IAC3B,SAAS,EAAE,EAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC;IAC/C,WAAW,EAAE,EAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAC;CAClD,CAAC;AAEF,oGAAoG;AACpG,MAAM,aAAa,GAA+B;IAC9C,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,SAAS;CACnB,CAAC;AAEF,uDAAuD;AACvD,MAAM,UAAU,WAAW,CACvB,OAA0D,EAC1D,KAA+B,EAC/B,WAAwB,OAAO;IAE/B,MAAM,KAAK,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAA2B,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/F,MAAM,CAAC,GAAG,KAAK,EAAE,OAAO,IAAK,OAA0C,IAAI,QAAQ,CAAC;IACpF,OAAO,EAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK,EAAE,KAAK,IAAI,aAAa,CAAC,CAAC,CAAC,EAAC,CAAC;AAC1E,CAAC"}
@@ -35,12 +35,13 @@ export declare const placeholderFill = "bg-foreground/10 hc:bg-border";
35
35
  export declare const selectedRow = "bg-accent text-accent-foreground shadow-[inset_2px_0_0_var(--primary-text)]";
36
36
  /**
37
37
  * Status surface shared by Alert and Callout (§2): one `soft` / `outline` axis, same values in both.
38
- * Expects a color variant (`--c`, `--c-tint`) on the same element. Neutral swaps the hue border for the
38
+ * Expects a color variant (`--c`, `--c-tint`) on the same element. Text, icons and links on it use
39
+ * `--c-text`, which each variant sets to the color that passes 4.5:1 on that surface. Neutral swaps the hue border for the
39
40
  * plain border on a card surface (`statusSurfaceNeutral`).
40
41
  */
41
42
  export declare const statusSurface: {
42
- readonly soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c)";
43
- readonly outline: "border-(--c)/50 bg-card hc:border-(--c)";
43
+ readonly soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c) [--c-text:var(--c-soft-text)]";
44
+ readonly outline: "border-(--c)/50 bg-card hc:border-(--c) [--c-text:var(--c)]";
44
45
  };
45
- export declare const statusSurfaceNeutral = "border-border bg-card hc:border-border";
46
+ export declare const statusSurfaceNeutral = "border-border bg-card hc:border-border [--c-text:var(--c)]";
46
47
  //# sourceMappingURL=recipes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/lib/recipes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wDAAwD;AACxD,eAAO,MAAM,SAAS,2FAA2F,CAAC;AAElH,2CAA2C;AAC3C,eAAO,MAAM,WAAW,gHACyF,CAAC;AAElH,iGAAiG;AACjG,eAAO,MAAM,YAAY,qNAC6L,CAAC;AAEvN,iFAAiF;AACjF,eAAO,MAAM,YAAY,+GACuF,CAAC;AAEjH,oCAAoC;AACpC,eAAO,MAAM,WAAW,yWACkV,CAAC;AAE3W;;;;GAIG;AACH,eAAO,MAAM,YAAY,8JACsI,CAAC;AAEhK,iFAAiF;AACjF,eAAO,MAAM,QAAQ,6nBAAmf,CAAC;AAEzgB,0CAA0C;AAC1C,eAAO,MAAM,SAAS,0DAA0D,CAAC;AAEjF,wCAAwC;AACxC,eAAO,MAAM,aAAa,8BAA8B,CAAC;AAEzD,8DAA8D;AAC9D,eAAO,MAAM,QAAQ,0LACsK,CAAC;AAE5L,4EAA4E;AAC5E,eAAO,MAAM,OAAO,4CAA4C,CAAC;AAEjE,iHAAiH;AACjH,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAE/D,yGAAyG;AACzG,eAAO,MAAM,WAAW,gFAAgF,CAAC;AAEzG;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;CAKhB,CAAC;AAEX,eAAO,MAAM,oBAAoB,2CAA2C,CAAC"}
1
+ {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/lib/recipes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wDAAwD;AACxD,eAAO,MAAM,SAAS,2FAA2F,CAAC;AAElH,2CAA2C;AAC3C,eAAO,MAAM,WAAW,gHACyF,CAAC;AAElH,iGAAiG;AACjG,eAAO,MAAM,YAAY,qNAC6L,CAAC;AAEvN,iFAAiF;AACjF,eAAO,MAAM,YAAY,+GACuF,CAAC;AAEjH,oCAAoC;AACpC,eAAO,MAAM,WAAW,yWACkV,CAAC;AAE3W;;;;GAIG;AACH,eAAO,MAAM,YAAY,8JACsI,CAAC;AAEhK,iFAAiF;AACjF,eAAO,MAAM,QAAQ,6nBAAmf,CAAC;AAEzgB,0CAA0C;AAC1C,eAAO,MAAM,SAAS,0DAA0D,CAAC;AAEjF,wCAAwC;AACxC,eAAO,MAAM,aAAa,8BAA8B,CAAC;AAEzD,8DAA8D;AAC9D,eAAO,MAAM,QAAQ,0LACsK,CAAC;AAE5L,4EAA4E;AAC5E,eAAO,MAAM,OAAO,4CAA4C,CAAC;AAEjE,iHAAiH;AACjH,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAE/D,yGAAyG;AACzG,eAAO,MAAM,WAAW,gFAAgF,CAAC;AAEzG;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;CAMhB,CAAC;AAEX,eAAO,MAAM,oBAAoB,+DAA+D,CAAC"}
@@ -35,14 +35,16 @@ export const placeholderFill = "bg-foreground/10 hc:bg-border";
35
35
  export const selectedRow = "bg-accent text-accent-foreground shadow-[inset_2px_0_0_var(--primary-text)]";
36
36
  /**
37
37
  * Status surface shared by Alert and Callout (§2): one `soft` / `outline` axis, same values in both.
38
- * Expects a color variant (`--c`, `--c-tint`) on the same element. Neutral swaps the hue border for the
38
+ * Expects a color variant (`--c`, `--c-tint`) on the same element. Text, icons and links on it use
39
+ * `--c-text`, which each variant sets to the color that passes 4.5:1 on that surface. Neutral swaps the hue border for the
39
40
  * plain border on a card surface (`statusSurfaceNeutral`).
40
41
  */
41
42
  export const statusSurface = {
42
- // status tint behind foreground text; the hue lives in the border, icon and accents (TermiColor rule)
43
- soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c)",
43
+ // status tint behind foreground text; the hue lives in the border, icon and accents (TermiColor rule).
44
+ // `--c-text` is the hue for text, icons and links on it: on the tint it is the soft text (4.5:1)
45
+ soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c) [--c-text:var(--c-soft-text)]",
44
46
  // no wash: card surface with a hue border, for dense layouts
45
- outline: "border-(--c)/50 bg-card hc:border-(--c)",
47
+ outline: "border-(--c)/50 bg-card hc:border-(--c) [--c-text:var(--c)]",
46
48
  };
47
- export const statusSurfaceNeutral = "border-border bg-card hc:border-border";
49
+ export const statusSurfaceNeutral = "border-border bg-card hc:border-border [--c-text:var(--c)]";
48
50
  //# sourceMappingURL=recipes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/lib/recipes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,wFAAwF,CAAC;AAElH,2CAA2C;AAC3C,MAAM,CAAC,MAAM,WAAW,GACpB,6GAA6G,CAAC;AAElH,iGAAiG;AACjG,MAAM,CAAC,MAAM,YAAY,GACrB,kNAAkN,CAAC;AAEvN,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GACrB,4GAA4G,CAAC;AAEjH,oCAAoC;AACpC,MAAM,CAAC,MAAM,WAAW,GACpB,sWAAsW,CAAC;AAE3W;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GACrB,2JAA2J,CAAC;AAEhK,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,YAAY,ieAAie,CAAC;AAEzgB,0CAA0C;AAC1C,MAAM,CAAC,MAAM,SAAS,GAAG,uDAAuD,CAAC;AAEjF,wCAAwC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAEzD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,QAAQ,GACjB,uLAAuL,CAAC,CAAC,0CAA0C;AAEvO,4EAA4E;AAC5E,MAAM,CAAC,MAAM,OAAO,GAAG,yCAAyC,CAAC;AAEjE,iHAAiH;AACjH,MAAM,CAAC,MAAM,eAAe,GAAG,+BAA+B,CAAC;AAE/D,yGAAyG;AACzG,MAAM,CAAC,MAAM,WAAW,GAAG,6EAA6E,CAAC;AAEzG;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,sGAAsG;IACtG,IAAI,EAAE,+CAA+C;IACrD,6DAA6D;IAC7D,OAAO,EAAE,yCAAyC;CAC5C,CAAC;AAEX,MAAM,CAAC,MAAM,oBAAoB,GAAG,wCAAwC,CAAC"}
1
+ {"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/lib/recipes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,wFAAwF,CAAC;AAElH,2CAA2C;AAC3C,MAAM,CAAC,MAAM,WAAW,GACpB,6GAA6G,CAAC;AAElH,iGAAiG;AACjG,MAAM,CAAC,MAAM,YAAY,GACrB,kNAAkN,CAAC;AAEvN,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GACrB,4GAA4G,CAAC;AAEjH,oCAAoC;AACpC,MAAM,CAAC,MAAM,WAAW,GACpB,sWAAsW,CAAC;AAE3W;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GACrB,2JAA2J,CAAC;AAEhK,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,YAAY,ieAAie,CAAC;AAEzgB,0CAA0C;AAC1C,MAAM,CAAC,MAAM,SAAS,GAAG,uDAAuD,CAAC;AAEjF,wCAAwC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAEzD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,QAAQ,GACjB,uLAAuL,CAAC,CAAC,0CAA0C;AAEvO,4EAA4E;AAC5E,MAAM,CAAC,MAAM,OAAO,GAAG,yCAAyC,CAAC;AAEjE,iHAAiH;AACjH,MAAM,CAAC,MAAM,eAAe,GAAG,+BAA+B,CAAC;AAE/D,yGAAyG;AACzG,MAAM,CAAC,MAAM,WAAW,GAAG,6EAA6E,CAAC;AAEzG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,uGAAuG;IACvG,iGAAiG;IACjG,IAAI,EAAE,6EAA6E;IACnF,6DAA6D;IAC7D,OAAO,EAAE,6DAA6D;CAChE,CAAC;AAEX,MAAM,CAAC,MAAM,oBAAoB,GAAG,4DAA4D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repraze/kernelkit",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "Modern, techy React components: every shadcn component on Base UI, Tailwind v4 and cva, themed by TermiColor.",
5
5
  "keywords": [
6
6
  "react",
@@ -93,7 +93,10 @@ function AlertDialogMedia({
93
93
  data-color={color}
94
94
  className={cn(
95
95
  "mb-2 inline-flex size-10 items-center justify-center rounded-lg border border-border bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-5",
96
- color && [colorVariants[color], "border-(--c)/40 bg-(--c-tint) text-foreground *:[svg]:text-(--c)"],
96
+ color && [
97
+ colorVariants[color],
98
+ "border-(--c)/40 bg-(--c-tint) text-foreground *:[svg]:text-(--c-soft-text)",
99
+ ],
97
100
  className,
98
101
  )}
99
102
  {...props}
@@ -17,8 +17,8 @@ const alertCva = cva(
17
17
  variants: {
18
18
  color: colorVariants,
19
19
  variant: {
20
- soft: [statusSurface.soft, "text-foreground *:[svg]:text-(--c)"],
21
- outline: [statusSurface.outline, "text-card-foreground *:[svg]:text-(--c)"],
20
+ soft: [statusSurface.soft, "text-foreground *:[svg]:text-(--c-text)"],
21
+ outline: [statusSurface.outline, "text-card-foreground *:[svg]:text-(--c-text)"],
22
22
  },
23
23
  },
24
24
  compoundVariants: [
@@ -73,7 +73,7 @@ function AlertTitle({className, ...props}: React.ComponentProps<"div">) {
73
73
  data-slot="alert-title"
74
74
  className={cn(
75
75
  heading,
76
- "group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-(--c)",
76
+ "group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-(--c-text)",
77
77
  className,
78
78
  )}
79
79
  {...props}
@@ -25,7 +25,7 @@ const badgeCva = cva(
25
25
  color: colorVariants,
26
26
  variant: {
27
27
  solid: "bg-(--c-fill) text-(--c-fill-fg) [a]:hover:bg-(--c-fill)/85",
28
- soft: "bg-(--c)/12 text-(--c) dark:bg-(--c)/20 [a]:hover:bg-(--c)/20 dark:[a]:hover:bg-(--c)/30",
28
+ soft: "bg-(--c)/12 text-(--c-soft-text) dark:bg-(--c)/20 [a]:hover:bg-(--c)/20 dark:[a]:hover:bg-(--c)/30",
29
29
  outline: "border-border text-foreground [a]:hover:bg-hover",
30
30
  ghost: "text-foreground [a]:hover:bg-hover",
31
31
  link: "text-(--c) underline-offset-4 hover:underline",
@@ -27,7 +27,7 @@ const buttonCva = cva(
27
27
  color: colorVariants,
28
28
  variant: {
29
29
  solid: "bg-(--c-fill) text-(--c-fill-fg) hover:bg-(--c-fill)/85 aria-expanded:bg-(--c-fill)/85",
30
- soft: "bg-(--c)/12 text-(--c) hover:bg-(--c)/20 aria-expanded:bg-(--c)/20 dark:bg-(--c)/20 dark:hover:bg-(--c)/30",
30
+ soft: "bg-(--c)/12 text-(--c-soft-text) hover:bg-(--c)/20 aria-expanded:bg-(--c)/20 dark:bg-(--c)/20 dark:hover:bg-(--c)/30",
31
31
  outline: "border-input bg-transparent hover:bg-hover aria-expanded:bg-hover",
32
32
  ghost: "text-foreground hover:bg-hover aria-expanded:bg-hover",
33
33
  link: "text-(--c) underline-offset-4 hover:underline",
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import {Button, buttonVariants} from "@repraze/kernelkit/components/button";
4
+ import {useLocale, weekStart} from "@repraze/kernelkit/components/locale";
4
5
  import {cn} from "cn";
5
6
  import {ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon} from "lucide-react";
6
7
  import * as React from "react";
@@ -13,6 +14,37 @@ import {type DayButton, DayPicker, getDefaultClassNames, type Locale} from "reac
13
14
  * - today: primary dot under the number + medium weight
14
15
  * - outside days: muted text; disabled: muted text + line-through
15
16
  */
17
+ /** DayPicker props minus `locale`, keeping the `mode` union intact (a plain Omit would collapse it). */
18
+ type CalendarBaseProps =
19
+ React.ComponentProps<typeof DayPicker> extends infer P ? (P extends unknown ? Omit<P, "locale"> : never) : never;
20
+
21
+ /** DayPicker formatters from Intl: the caption, weekday names, day numbers (native digits) and dropdowns. */
22
+ function intlFormatters(locale: string) {
23
+ try {
24
+ const caption = new Intl.DateTimeFormat(locale, {month: "long", year: "numeric"});
25
+ // "short" is the full name in some languages (Arabic): fall back to "narrow" when it won't fit a cell
26
+ const short = new Intl.DateTimeFormat(locale, {weekday: "short"});
27
+ const long = Array.from({length: 7}, (_, i) => short.format(new Date(2000, 0, 2 + i))).some(
28
+ (d) => d.length > 4,
29
+ );
30
+ const weekday = long ? new Intl.DateTimeFormat(locale, {weekday: "narrow"}) : short;
31
+ const day = new Intl.DateTimeFormat(locale, {day: "numeric"});
32
+ const month = new Intl.DateTimeFormat(locale, {month: "short"});
33
+ const year = new Intl.DateTimeFormat(locale, {year: "numeric"});
34
+ return {
35
+ formatCaption: (date: Date) => caption.format(date),
36
+ formatWeekdayName: (date: Date) => weekday.format(date),
37
+ // the day part only: ja formats "14日", the grid wants "14" (native digits kept: "١٤" in ar-EG)
38
+ formatDay: (date: Date) =>
39
+ day.formatToParts(date).find((p) => p.type === "day")?.value ?? String(date.getDate()),
40
+ formatMonthDropdown: (date: Date) => month.format(date),
41
+ formatYearDropdown: (date: Date) => year.format(date),
42
+ };
43
+ } catch {
44
+ return {};
45
+ }
46
+ }
47
+
16
48
  function Calendar({
17
49
  className,
18
50
  classNames,
@@ -22,11 +54,20 @@ function Calendar({
22
54
  locale,
23
55
  formatters,
24
56
  components,
57
+ weekStartsOn,
25
58
  ...props
26
- }: React.ComponentProps<typeof DayPicker> & {
59
+ }: CalendarBaseProps & {
27
60
  buttonVariant?: React.ComponentProps<typeof Button>["variant"];
61
+ /**
62
+ * A BCP 47 tag (`"fr-FR"`) or nothing: month and weekday names, digits and the first day of the week
63
+ * come from `Intl` (prop › LocaleProvider › browser). A date-fns `Locale` object uses date-fns instead.
64
+ */
65
+ locale?: Partial<Locale> | string;
28
66
  }) {
29
67
  const defaultClassNames = getDefaultClassNames();
68
+ const dateFnsLocale = typeof locale === "object" ? locale : undefined;
69
+ const tag = useLocale(typeof locale === "string" ? locale : undefined);
70
+ const intl = React.useMemo(() => (dateFnsLocale ? null : intlFormatters(tag)), [dateFnsLocale, tag]);
30
71
 
31
72
  return (
32
73
  <DayPicker
@@ -38,9 +79,12 @@ function Calendar({
38
79
  className,
39
80
  )}
40
81
  captionLayout={captionLayout}
41
- locale={locale}
82
+ locale={dateFnsLocale}
83
+ lang={dateFnsLocale ? undefined : tag}
84
+ weekStartsOn={weekStartsOn ?? (dateFnsLocale ? undefined : weekStart(tag))}
42
85
  formatters={{
43
- formatMonthDropdown: (date) => date.toLocaleString(locale?.code, {month: "short"}),
86
+ formatMonthDropdown: (date) => date.toLocaleString(dateFnsLocale?.code ?? tag, {month: "short"}),
87
+ ...intl,
44
88
  ...formatters,
45
89
  }}
46
90
  classNames={{
@@ -128,7 +172,7 @@ function Calendar({
128
172
 
129
173
  return <ChevronDownIcon className={cn("size-4", className)} {...props} />;
130
174
  },
131
- DayButton: ({...props}) => <CalendarDayButton locale={locale} {...props} />,
175
+ DayButton: ({...props}) => <CalendarDayButton locale={dateFnsLocale} {...props} />,
132
176
  WeekNumber: ({children, ...props}) => {
133
177
  return (
134
178
  <td {...props}>
@@ -41,10 +41,10 @@ const calloutVariants = cva(
41
41
  );
42
42
 
43
43
  const calloutHeader =
44
- "flex min-h-8 w-full items-center gap-1.5 py-1.5 pr-3 pl-4 text-left text-xs text-(--c) group-data-[color=neutral]/callout:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5";
44
+ "flex min-h-8 w-full items-center gap-1.5 py-1.5 pr-3 pl-4 text-left text-xs text-(--c-text) group-data-[color=neutral]/callout:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5";
45
45
 
46
46
  const calloutBody =
47
- "py-2 pr-3 pl-4 text-sm leading-relaxed [&_a]:text-(--c) [&_a]:underline [&_a]:underline-offset-3 group-data-[color=neutral]/callout:[&_a]:text-foreground [&_p:not(:last-child)]:mb-2 border-(--callout-line)";
47
+ "py-2 pr-3 pl-4 text-sm leading-relaxed [&_a]:text-(--c-text) [&_a]:underline [&_a]:underline-offset-3 group-data-[color=neutral]/callout:[&_a]:text-foreground [&_p:not(:last-child)]:mb-2 border-(--callout-line)";
48
48
 
49
49
  interface CalloutProps extends Omit<React.ComponentProps<"div">, "color" | "title"> {
50
50
  /** Status or hue of the indicator, header and border. */
@@ -229,7 +229,7 @@ function ComboboxChip({
229
229
  <ComboboxPrimitive.Chip
230
230
  data-slot="combobox-chip"
231
231
  className={cn(
232
- "flex h-5.5 w-fit items-center justify-center gap-1 rounded-md border border-border bg-secondary px-1.5 text-xs font-medium whitespace-nowrap text-secondary-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0 outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50",
232
+ "flex h-5.5 w-fit items-center justify-center gap-1 rounded-md border border-border bg-foreground/10 px-1.5 text-xs font-medium whitespace-nowrap text-foreground hc:bg-transparent has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0 outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50",
233
233
  className,
234
234
  )}
235
235
  {...props}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {Button} from "@repraze/kernelkit/components/button";
4
4
  import {Calendar} from "@repraze/kernelkit/components/calendar";
5
+ import {useLocale} from "@repraze/kernelkit/components/locale";
5
6
  import {Popover, PopoverContent, PopoverTrigger} from "@repraze/kernelkit/components/popover";
6
7
  import {cn} from "cn";
7
8
  import {format as formatDate} from "date-fns";
@@ -14,8 +15,13 @@ type CalendarProps = React.ComponentProps<typeof Calendar>;
14
15
  interface DatePickerBaseProps {
15
16
  /** Trigger text when nothing is picked. */
16
17
  placeholder?: React.ReactNode;
17
- /** date-fns format string for the trigger label. */
18
- format?: string;
18
+ /**
19
+ * Trigger label: `Intl.DateTimeFormat` options (default `{dateStyle: "long"}`, `"medium"` for ranges), a
20
+ * function, or a date-fns pattern string.
21
+ */
22
+ format?: DateFormat;
23
+ /** BCP 47 tag for the label and the calendar; defaults to LocaleProvider › browser. */
24
+ locale?: string;
19
25
  size?: "sm" | "default" | "lg";
20
26
  disabled?: boolean;
21
27
  "aria-invalid"?: boolean;
@@ -27,6 +33,27 @@ interface DatePickerBaseProps {
27
33
  align?: "start" | "center" | "end";
28
34
  }
29
35
 
36
+ type DateFormat = Intl.DateTimeFormatOptions | string | ((date: Date) => string);
37
+
38
+ /** One date in the trigger. */
39
+ function formatOne(date: Date, format: DateFormat, locale: string) {
40
+ if (typeof format === "function") {
41
+ return format(date);
42
+ }
43
+ if (typeof format === "string") {
44
+ return formatDate(date, format);
45
+ }
46
+ return new Intl.DateTimeFormat(locale, format).format(date);
47
+ }
48
+
49
+ /** A range in the trigger: Intl's formatRange shares what both ends have in common ("Nov 14 – 20, 2026"). */
50
+ function formatRange(from: Date, to: Date, format: DateFormat, locale: string) {
51
+ if (typeof format === "object") {
52
+ return new Intl.DateTimeFormat(locale, format).formatRange(from, to);
53
+ }
54
+ return `${formatOne(from, format, locale)} – ${formatOne(to, format, locale)}`;
55
+ }
56
+
30
57
  function useControllable<T>(value: T | undefined, defaultValue: T | undefined, onChange?: (value: T) => void) {
31
58
  const [inner, setInner] = React.useState(defaultValue);
32
59
  const controlled = value !== undefined;
@@ -59,7 +86,8 @@ function DatePickerTrigger({
59
86
  variant="outline"
60
87
  size={size}
61
88
  className={cn(
62
- "w-60 justify-start text-left font-normal data-empty:text-muted-foreground data-popup-open:bg-hover [&>svg]:text-muted-foreground",
89
+ // a field like Select's trigger, not an outline button: it sits beside inputs and TimeInput
90
+ "w-60 justify-start bg-field text-start font-normal hover:bg-field-hover aria-expanded:bg-field-hover data-empty:text-muted-foreground data-popup-open:bg-field-hover [&>svg]:text-muted-foreground",
63
91
  className,
64
92
  )}
65
93
  {...props}
@@ -84,7 +112,8 @@ function DatePicker({
84
112
  defaultValue = null,
85
113
  onValueChange,
86
114
  placeholder = "Pick a date",
87
- format = "PPP",
115
+ format = {dateStyle: "long"},
116
+ locale: localeProp,
88
117
  calendarProps,
89
118
  align = "start",
90
119
  className,
@@ -92,17 +121,19 @@ function DatePicker({
92
121
  }: DatePickerProps) {
93
122
  const [open, setOpen] = React.useState(false);
94
123
  const [date, setDate] = useControllable<Date | null>(value, defaultValue, onValueChange);
124
+ const locale = useLocale(localeProp);
95
125
 
96
126
  return (
97
127
  <Popover open={open} onOpenChange={setOpen}>
98
128
  <DatePickerTrigger
99
- label={date ? formatDate(date, format) : undefined}
129
+ label={date ? formatOne(date, format, locale) : undefined}
100
130
  placeholder={placeholder}
101
131
  className={className}
102
132
  {...trigger}
103
133
  />
104
134
  <PopoverContent data-slot="date-picker-content" className="w-auto p-0" align={align}>
105
135
  <Calendar
136
+ locale={locale}
106
137
  {...calendarProps}
107
138
  mode="single"
108
139
  selected={date ?? undefined}
@@ -131,7 +162,8 @@ function DateRangePicker({
131
162
  defaultValue = null,
132
163
  onValueChange,
133
164
  placeholder = "Pick a date range",
134
- format = "LLL d, y",
165
+ format = {dateStyle: "medium"},
166
+ locale: localeProp,
135
167
  numberOfMonths = 2,
136
168
  calendarProps,
137
169
  align = "start",
@@ -139,10 +171,11 @@ function DateRangePicker({
139
171
  ...trigger
140
172
  }: DateRangePickerProps) {
141
173
  const [range, setRange] = useControllable<DateRange | null>(value, defaultValue, onValueChange);
174
+ const locale = useLocale(localeProp);
142
175
  const label = range?.from
143
176
  ? range.to
144
- ? `${formatDate(range.from, format)} – ${formatDate(range.to, format)}`
145
- : formatDate(range.from, format)
177
+ ? formatRange(range.from, range.to, format, locale)
178
+ : formatOne(range.from, format, locale)
146
179
  : undefined;
147
180
 
148
181
  return (
@@ -150,6 +183,7 @@ function DateRangePicker({
150
183
  <DatePickerTrigger label={label} placeholder={placeholder} className={cn("w-72", className)} {...trigger} />
151
184
  <PopoverContent data-slot="date-picker-content" className="w-auto p-0" align={align}>
152
185
  <Calendar
186
+ locale={locale}
153
187
  {...calendarProps}
154
188
  mode="range"
155
189
  numberOfMonths={numberOfMonths}
@@ -163,4 +197,4 @@ function DateRangePicker({
163
197
  }
164
198
 
165
199
  export {DatePicker, DatePickerTrigger, DateRangePicker};
166
- export type {DatePickerProps, DateRangePickerProps};
200
+ export type {DateFormat, DatePickerProps, DateRangePickerProps};
@@ -0,0 +1,107 @@
1
+ "use client";
2
+
3
+ import {cva} from "class-variance-authority";
4
+ import {cn} from "cn";
5
+ import * as React from "react";
6
+
7
+ type DescriptionListOrientation = "horizontal" | "vertical";
8
+
9
+ const DescriptionListContext = React.createContext<{orientation: DescriptionListOrientation; divided: boolean}>({
10
+ orientation: "horizontal",
11
+ divided: false,
12
+ });
13
+
14
+ const descriptionListVariants = cva("text-sm", {
15
+ variants: {
16
+ orientation: {
17
+ // one shared term column: every item is a subgrid row, so terms line up across rows
18
+ horizontal: "grid grid-cols-[minmax(0,max-content)_minmax(0,1fr)] gap-x-6",
19
+ vertical: "flex flex-col",
20
+ },
21
+ divided: {
22
+ true: "",
23
+ false: "",
24
+ },
25
+ },
26
+ compoundVariants: [
27
+ {orientation: "horizontal", divided: false, class: "gap-y-2.5"},
28
+ {orientation: "vertical", divided: false, class: "gap-4"},
29
+ ],
30
+ defaultVariants: {orientation: "horizontal", divided: false},
31
+ });
32
+
33
+ interface DescriptionListProps extends React.ComponentProps<"dl"> {
34
+ /** `horizontal`: terms in a column beside their details. `vertical`: each term above its details. */
35
+ orientation?: DescriptionListOrientation;
36
+ /** Rule between items. */
37
+ divided?: boolean;
38
+ }
39
+
40
+ /** Key / value pairs: properties of a server, an order, a file. Terms in the label face, details in the body face. */
41
+ function DescriptionList({orientation = "horizontal", divided = false, className, ...props}: DescriptionListProps) {
42
+ const ctx = React.useMemo(() => ({orientation, divided}), [orientation, divided]);
43
+ return (
44
+ <DescriptionListContext.Provider value={ctx}>
45
+ <dl
46
+ data-slot="description-list"
47
+ data-orientation={orientation}
48
+ data-divided={divided ? "" : undefined}
49
+ className={cn(descriptionListVariants({orientation, divided}), className)}
50
+ {...props}
51
+ />
52
+ </DescriptionListContext.Provider>
53
+ );
54
+ }
55
+
56
+ const descriptionItemVariants = cva("min-w-0", {
57
+ variants: {
58
+ orientation: {
59
+ horizontal: "col-span-2 grid grid-cols-subgrid items-baseline",
60
+ vertical: "flex flex-col gap-1",
61
+ },
62
+ divided: {
63
+ true: "border-b border-border py-2.5 first:pt-0 last:border-b-0 last:pb-0",
64
+ false: "",
65
+ },
66
+ },
67
+ defaultVariants: {orientation: "horizontal", divided: false},
68
+ });
69
+
70
+ /** One term with its details (a `<div>`, which `<dl>` allows around each group). */
71
+ function DescriptionItem({className, ...props}: React.ComponentProps<"div">) {
72
+ const {orientation, divided} = React.useContext(DescriptionListContext);
73
+ return (
74
+ <div
75
+ data-slot="description-item"
76
+ data-orientation={orientation}
77
+ data-divided={divided ? "" : undefined}
78
+ className={cn(descriptionItemVariants({orientation, divided}), className)}
79
+ {...props}
80
+ />
81
+ );
82
+ }
83
+
84
+ function DescriptionTerm({className, ...props}: React.ComponentProps<"dt">) {
85
+ return (
86
+ <dt
87
+ data-slot="description-term"
88
+ className={cn("min-w-0 font-alt text-xs text-muted-foreground", className)}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ /** The value. Inline content (a status dot, a copy button) sits on one row with `flex items-center gap-2`. */
95
+ function DescriptionDetails({className, ...props}: React.ComponentProps<"dd">) {
96
+ return <dd data-slot="description-details" className={cn("min-w-0 text-foreground", className)} {...props} />;
97
+ }
98
+
99
+ export {
100
+ DescriptionDetails,
101
+ DescriptionItem,
102
+ descriptionItemVariants,
103
+ DescriptionList,
104
+ descriptionListVariants,
105
+ DescriptionTerm,
106
+ };
107
+ export type {DescriptionListOrientation, DescriptionListProps};
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ /**
6
+ * Locale for the kit's dates and times (Calendar, DatePicker, TimeInput, TimeSelect), all formatted with
7
+ * `Intl`. Resolution: a component's own `locale` prop › the nearest `LocaleProvider` › the browser's
8
+ * language. Hydration-safe: the server render and the hydration pass use `FALLBACK_LOCALE` (the browser's
9
+ * language is unknown on the server), then the client re-renders with the browser's. To get it right from
10
+ * the first paint in SSR, pass the request's locale (Accept-Language, user setting) to `LocaleProvider`.
11
+ */
12
+ const FALLBACK_LOCALE = "en-US";
13
+
14
+ const LocaleContext = React.createContext<string | undefined>(undefined);
15
+
16
+ function LocaleProvider({locale, children}: {locale?: string; children: React.ReactNode}) {
17
+ return <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>;
18
+ }
19
+
20
+ function browserLocale(): string | null {
21
+ try {
22
+ const tag = navigator.languages?.[0] ?? navigator.language;
23
+ return tag && Intl.DateTimeFormat.supportedLocalesOf(tag).length ? tag : null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ function subscribe(onChange: () => void) {
30
+ window.addEventListener("languagechange", onChange);
31
+ return () => window.removeEventListener("languagechange", onChange);
32
+ }
33
+
34
+ /** The locale to format with: `explicit` › LocaleProvider › browser (after hydration) › `FALLBACK_LOCALE`. */
35
+ function useLocale(explicit?: string): string {
36
+ const provided = React.useContext(LocaleContext);
37
+ const browser = React.useSyncExternalStore(subscribe, browserLocale, () => null);
38
+ return explicit ?? provided ?? browser ?? FALLBACK_LOCALE;
39
+ }
40
+
41
+ // Regions whose week starts on Sunday or Saturday (CLDR), for engines without Intl.Locale#getWeekInfo.
42
+ const SUNDAY = new Set(
43
+ "AG AS AU BD BR BS BT BW BZ CA CN CO DM DO ET GT GU HK HN ID IL IN JM JP KE KH KR LA MH MM MO MT MX MZ NI NP PA PE PH PK PR PT PY SA SG SV TH TT TW UM US VE VI WS YE ZA ZW".split(
44
+ " ",
45
+ ),
46
+ );
47
+ const SATURDAY = new Set("AE AF BH DJ DZ EG IQ IR JO KW LY OM QA SD SY".split(" "));
48
+
49
+ /** First day of the week for a locale: 0 Sunday … 6 Saturday. */
50
+ function weekStart(locale: string): 0 | 1 | 6 {
51
+ try {
52
+ const loc = new Intl.Locale(locale) as Intl.Locale & {
53
+ getWeekInfo?: () => {firstDay: number};
54
+ weekInfo?: {firstDay: number};
55
+ };
56
+ const info = loc.getWeekInfo?.() ?? loc.weekInfo;
57
+ if (info) {
58
+ return (info.firstDay % 7) as 0 | 1 | 6;
59
+ }
60
+ const region = loc.maximize().region ?? "";
61
+ return SUNDAY.has(region) ? 0 : SATURDAY.has(region) ? 6 : 1;
62
+ } catch {
63
+ return 0;
64
+ }
65
+ }
66
+
67
+ export {FALLBACK_LOCALE, LocaleProvider, useLocale, weekStart};