@repraze/kernelkit 0.1.0-alpha.4 → 0.1.0-alpha.6

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 (125) 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/pager.d.ts +1 -1
  31. package/dist/components/pager.d.ts.map +1 -1
  32. package/dist/components/pager.js +4 -4
  33. package/dist/components/pager.js.map +1 -1
  34. package/dist/components/popover.d.ts +1 -1
  35. package/dist/components/popover.d.ts.map +1 -1
  36. package/dist/components/popover.js +2 -2
  37. package/dist/components/popover.js.map +1 -1
  38. package/dist/components/progress.d.ts +19 -6
  39. package/dist/components/progress.d.ts.map +1 -1
  40. package/dist/components/progress.js +18 -3
  41. package/dist/components/progress.js.map +1 -1
  42. package/dist/components/search-trigger.d.ts +14 -4
  43. package/dist/components/search-trigger.d.ts.map +1 -1
  44. package/dist/components/search-trigger.js +25 -4
  45. package/dist/components/search-trigger.js.map +1 -1
  46. package/dist/components/shimmer.d.ts +13 -0
  47. package/dist/components/shimmer.d.ts.map +1 -0
  48. package/dist/components/shimmer.js +21 -0
  49. package/dist/components/shimmer.js.map +1 -0
  50. package/dist/components/spinner.d.ts +28 -6
  51. package/dist/components/spinner.d.ts.map +1 -1
  52. package/dist/components/spinner.js +66 -14
  53. package/dist/components/spinner.js.map +1 -1
  54. package/dist/components/stepper.d.ts +64 -0
  55. package/dist/components/stepper.d.ts.map +1 -0
  56. package/dist/components/stepper.js +167 -0
  57. package/dist/components/stepper.js.map +1 -0
  58. package/dist/components/tag-input.d.ts +47 -0
  59. package/dist/components/tag-input.d.ts.map +1 -0
  60. package/dist/components/tag-input.js +211 -0
  61. package/dist/components/tag-input.js.map +1 -0
  62. package/dist/components/time-input.d.ts +75 -0
  63. package/dist/components/time-input.d.ts.map +1 -0
  64. package/dist/components/time-input.js +341 -0
  65. package/dist/components/time-input.js.map +1 -0
  66. package/dist/components/time-select.d.ts +49 -0
  67. package/dist/components/time-select.d.ts.map +1 -0
  68. package/dist/components/time-select.js +178 -0
  69. package/dist/components/time-select.js.map +1 -0
  70. package/dist/components/timeline.d.ts +41 -0
  71. package/dist/components/timeline.d.ts.map +1 -0
  72. package/dist/components/timeline.js +83 -0
  73. package/dist/components/timeline.js.map +1 -0
  74. package/dist/components/toast.d.ts.map +1 -1
  75. package/dist/components/toast.js +4 -2
  76. package/dist/components/toast.js.map +1 -1
  77. package/dist/components/tree.d.ts +87 -0
  78. package/dist/components/tree.d.ts.map +1 -0
  79. package/dist/components/tree.js +516 -0
  80. package/dist/components/tree.js.map +1 -0
  81. package/dist/hooks/use-time-format.d.ts +23 -0
  82. package/dist/hooks/use-time-format.d.ts.map +1 -0
  83. package/dist/hooks/use-time-format.js +52 -0
  84. package/dist/hooks/use-time-format.js.map +1 -0
  85. package/dist/lib/color.d.ts +5 -1
  86. package/dist/lib/color.d.ts.map +1 -1
  87. package/dist/lib/color.js +14 -2
  88. package/dist/lib/color.js.map +1 -1
  89. package/dist/lib/recipes.d.ts +11 -6
  90. package/dist/lib/recipes.d.ts.map +1 -1
  91. package/dist/lib/recipes.js +13 -7
  92. package/dist/lib/recipes.js.map +1 -1
  93. package/dist/site/suite-header.d.ts +4 -1
  94. package/dist/site/suite-header.d.ts.map +1 -1
  95. package/dist/site/suite-header.js +7 -2
  96. package/dist/site/suite-header.js.map +1 -1
  97. package/package.json +1 -1
  98. package/src/components/alert-dialog.tsx +4 -1
  99. package/src/components/alert.tsx +3 -3
  100. package/src/components/badge.tsx +1 -1
  101. package/src/components/button.tsx +1 -1
  102. package/src/components/calendar.tsx +48 -4
  103. package/src/components/callout.tsx +2 -2
  104. package/src/components/combobox.tsx +1 -1
  105. package/src/components/date-picker.tsx +43 -9
  106. package/src/components/description-list.tsx +107 -0
  107. package/src/components/locale.tsx +67 -0
  108. package/src/components/pager.tsx +8 -8
  109. package/src/components/popover.tsx +3 -1
  110. package/src/components/progress.tsx +70 -4
  111. package/src/components/search-trigger.tsx +40 -7
  112. package/src/components/shimmer.tsx +36 -0
  113. package/src/components/spinner.tsx +126 -23
  114. package/src/components/stepper.tsx +346 -0
  115. package/src/components/tag-input.tsx +345 -0
  116. package/src/components/time-input.tsx +578 -0
  117. package/src/components/time-select.tsx +291 -0
  118. package/src/components/timeline.tsx +190 -0
  119. package/src/components/toast.tsx +4 -4
  120. package/src/components/tree.tsx +832 -0
  121. package/src/hooks/use-time-format.ts +72 -0
  122. package/src/lib/color.ts +19 -2
  123. package/src/lib/recipes.ts +13 -7
  124. package/src/site/suite-header.tsx +25 -4
  125. package/src/styles/kernelkit.css +56 -0
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import {useLocale} from "@repraze/kernelkit/components/locale";
4
+ import * as React from "react";
5
+
6
+ /** `"auto"` follows the locale: 12 in en-US, 24 in fr-FR, en-GB or ja-JP. */
7
+ type HourCyclePreference = 12 | 24 | "auto";
8
+
9
+ interface TimeFormat {
10
+ locale: string;
11
+ hourCycle: 12 | 24;
12
+ /** Localized day periods: "AM" / "PM" in en-US, "a.m." / "p.m." in en-CA, "오전" / "오후" in ko. */
13
+ am: string;
14
+ pm: string;
15
+ /** The day period comes before the hour (ko, zh). */
16
+ periodFirst: boolean;
17
+ /** "09:30" (24h) or "9:30 AM" / "오전 9:30" (12h), in the locale's own pattern. */
18
+ format: (minutes: number) => string;
19
+ /** A two-digit segment in the locale's digits: "05", "٠٥" in ar-EG. */
20
+ digits: (n: number) => string;
21
+ }
22
+
23
+ const pad = (n: number) => String(n).padStart(2, "0");
24
+
25
+ /**
26
+ * Hour cycle, day-period labels and a formatter, from `Intl` for the resolved locale (see `useLocale`:
27
+ * prop › LocaleProvider › browser, hydration-safe). An explicit 12 / 24 overrides the locale's cycle.
28
+ */
29
+ function useTimeFormat(preference: HourCyclePreference = "auto", explicitLocale?: string): TimeFormat {
30
+ const locale = useLocale(explicitLocale);
31
+ return React.useMemo(() => {
32
+ let hourCycle: 12 | 24 = preference === "auto" ? 24 : preference;
33
+ let am = "AM";
34
+ let pm = "PM";
35
+ let periodFirst = false;
36
+ let formatter: Intl.DateTimeFormat | null = null;
37
+ let numbers: Intl.NumberFormat | null = null;
38
+ try {
39
+ numbers = new Intl.NumberFormat(locale, {minimumIntegerDigits: 2, useGrouping: false});
40
+ if (preference === "auto") {
41
+ const hc = new Intl.DateTimeFormat(locale, {hour: "numeric"}).resolvedOptions().hourCycle;
42
+ hourCycle = hc === "h11" || hc === "h12" ? 12 : 24;
43
+ }
44
+ const f = new Intl.DateTimeFormat(locale, {hour: "numeric", hourCycle: "h12"});
45
+ const parts = (h: number) => f.formatToParts(new Date(2000, 0, 1, h));
46
+ am = parts(9).find((p) => p.type === "dayPeriod")?.value ?? am;
47
+ pm = parts(21).find((p) => p.type === "dayPeriod")?.value ?? pm;
48
+ const types = parts(9).map((p) => p.type);
49
+ periodFirst = types.includes("dayPeriod") && types.indexOf("dayPeriod") < types.indexOf("hour");
50
+ formatter = new Intl.DateTimeFormat(locale, {
51
+ hour: hourCycle === 24 ? "2-digit" : "numeric",
52
+ minute: "2-digit",
53
+ hourCycle: hourCycle === 24 ? "h23" : "h12",
54
+ });
55
+ } catch {
56
+ formatter = null;
57
+ }
58
+ const format = (minutes: number) => {
59
+ const h = Math.floor(minutes / 60);
60
+ const m = minutes % 60;
61
+ if (formatter) {
62
+ return formatter.format(new Date(2000, 0, 1, h, m));
63
+ }
64
+ return hourCycle === 24 ? `${pad(h)}:${pad(m)}` : `${h % 12 || 12}:${pad(m)} ${h < 12 ? am : pm}`;
65
+ };
66
+ const digits = (n: number) => (numbers ? numbers.format(n) : pad(n));
67
+ return {locale, hourCycle, am, pm, periodFirst, format, digits};
68
+ }, [preference, locale]);
69
+ }
70
+
71
+ export {useTimeFormat};
72
+ export type {HourCyclePreference, TimeFormat};
package/src/lib/color.ts CHANGED
@@ -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
 
14
18
  export const STATUS_COLORS = ["danger", "warning", "success", "info"] as const;
@@ -17,8 +21,17 @@ export const COLORS = ["neutral", "primary", ...STATUS_COLORS, ...HUES] as const
17
21
 
18
22
  export type Color = (typeof COLORS)[number];
19
23
 
24
+ /**
25
+ * Derived from `--c` on the same element, so it is set alongside every color (a :root rule would see no `--c`).
26
+ * The mix toward the (neutral) foreground sets the lightness for contrast but also scales chroma by 0.75, so
27
+ * where relative color syntax is supported the chroma is multiplied back by 1/0.75: same lightness and hue,
28
+ * the original saturation (≥ 5.08:1 everywhere, vs ≥ 5.68 for the plain mix kept as the fallback).
29
+ */
30
+ const SOFT_TEXT =
31
+ "[--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)]";
32
+
20
33
  // Literal strings on purpose: Tailwind only generates classes it can read in the source.
21
- export const colorVariants: Record<Color, string> = {
34
+ const baseColorVariants: Record<Color, string> = {
22
35
  neutral:
23
36
  "[--c:var(--foreground)] [--c-fg:var(--background)] [--c-tint:var(--secondary)] [--c-fill:var(--foreground)] [--c-fill-fg:var(--background)]",
24
37
  primary:
@@ -39,6 +52,10 @@ export const colorVariants: Record<Color, string> = {
39
52
  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)]",
40
53
  };
41
54
 
55
+ export const colorVariants = Object.fromEntries(
56
+ Object.entries(baseColorVariants).map(([color, vars]) => [color, `${vars} ${SOFT_TEXT}`]),
57
+ ) as Record<Color, string>;
58
+
42
59
  /** Every color except neutral: the ones whose outline/ghost variants are tinted. */
43
60
  export const TINTED_COLORS = COLORS.filter((c) => c !== "neutral");
44
61
 
@@ -40,9 +40,13 @@ export const listLabel = "px-2 py-1.5 text-xs font-medium text-muted-foreground"
40
40
  /** Separator inside a popup or list. */
41
41
  export const listSeparator = "-mx-1 my-1 h-px bg-border";
42
42
 
43
- /** Modal backdrop for dialog, alert dialog, sheet, drawer. */
43
+ /**
44
+ * Modal backdrop for dialog, alert dialog, sheet, drawer. A transition on Base UI's starting/ending styles,
45
+ * not a keyframe: the backdrop stays mounted until the slower panel finishes, and a finished keyframe
46
+ * (no fill) snapped it back to full dim and blur for that wait, a flash on close.
47
+ */
44
48
  export const backdrop =
45
- "fixed inset-0 isolate z-50 bg-black/40 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0"; // lint-design-ignore: scrim is not a role
49
+ "fixed inset-0 isolate z-50 bg-black/40 transition-opacity duration-150 ease-out motion-reduce:transition-none supports-backdrop-filter:backdrop-blur-xs data-starting-style:opacity-0 data-ending-style:opacity-0"; // lint-design-ignore: scrim is not a role
46
50
 
47
51
  /** Title in cards, dialogs, sheets, popovers, empty states, alerts (§5). */
48
52
  export const heading = "font-heading font-medium tracking-tight";
@@ -55,14 +59,16 @@ export const selectedRow = "bg-accent text-accent-foreground shadow-[inset_2px_0
55
59
 
56
60
  /**
57
61
  * Status surface shared by Alert and Callout (§2): one `soft` / `outline` axis, same values in both.
58
- * Expects a color variant (`--c`, `--c-tint`) on the same element. Neutral swaps the hue border for the
62
+ * Expects a color variant (`--c`, `--c-tint`) on the same element. Text, icons and links on it use
63
+ * `--c-text`, which each variant sets to the color that passes 4.5:1 on that surface. Neutral swaps the hue border for the
59
64
  * plain border on a card surface (`statusSurfaceNeutral`).
60
65
  */
61
66
  export const statusSurface = {
62
- // status tint behind foreground text; the hue lives in the border, icon and accents (TermiColor rule)
63
- soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c)",
67
+ // status tint behind foreground text; the hue lives in the border, icon and accents (TermiColor rule).
68
+ // `--c-text` is the hue for text, icons and links on it: on the tint it is the soft text (4.5:1)
69
+ soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c) [--c-text:var(--c-soft-text)]",
64
70
  // no wash: card surface with a hue border, for dense layouts
65
- outline: "border-(--c)/50 bg-card hc:border-(--c)",
71
+ outline: "border-(--c)/50 bg-card hc:border-(--c) [--c-text:var(--c)]",
66
72
  } as const;
67
73
 
68
- export const statusSurfaceNeutral = "border-border bg-card hc:border-border";
74
+ export const statusSurfaceNeutral = "border-border bg-card hc:border-border [--c-text:var(--c)]";
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import {Button} from "@repraze/kernelkit/components/button";
4
+ import {type SearchHandler, SearchRelay, SearchTrigger} from "@repraze/kernelkit/components/search-trigger";
4
5
  import {Separator} from "@repraze/kernelkit/components/separator";
5
6
  import {
6
7
  SiteHeader,
@@ -13,7 +14,7 @@ import {
13
14
  } from "@repraze/kernelkit/components/site-header";
14
15
  import {ThemeMenu} from "@repraze/kernelkit/components/theme-menu";
15
16
  import type * as React from "react";
16
- import {useEffect, useState} from "react";
17
+ import {useEffect, useRef, useState} from "react";
17
18
 
18
19
  import {GithubIcon} from "./github-icon";
19
20
 
@@ -36,7 +37,10 @@ export interface SuiteHeaderProps {
36
37
  nav: SuiteNavItem[];
37
38
  /** Accessible name of the nav (default "Main"). */
38
39
  navLabel?: string;
39
- /** The search (DocsSearch, or the site's own), before the separator. */
40
+ /**
41
+ * The search (DocsSearch, or the site's own built on SearchTrigger), before the separator. Below `md`
42
+ * it moves to the top of the phone menu: a full-width trigger there opens the same dialog.
43
+ */
40
44
  search?: React.ReactNode;
41
45
  /** Replaces the default `<ThemeMenu />`. */
42
46
  themeMenu?: React.ReactNode;
@@ -66,6 +70,7 @@ export function SuiteHeader({
66
70
  }: SuiteHeaderProps) {
67
71
  const [menuOpen, setMenuOpen] = useState(false);
68
72
  useEffect(() => setMenuOpen(false), [locationKey]);
73
+ const openSearch = useRef<SearchHandler | null>(null);
69
74
 
70
75
  const navLinks = nav.map((item) => (
71
76
  <SiteNavLink
@@ -82,6 +87,18 @@ export function SuiteHeader({
82
87
  <SiteHeader>
83
88
  <SiteHeaderContent>
84
89
  <SiteHeaderMenu title={brand} open={menuOpen} onOpenChange={setMenuOpen}>
90
+ {search ? (
91
+ <div className="px-2 pt-2">
92
+ <SearchTrigger
93
+ expanded
94
+ aria-label="Search"
95
+ onClick={(event) => {
96
+ setMenuOpen(false);
97
+ openSearch.current?.(event);
98
+ }}
99
+ />
100
+ </div>
101
+ ) : null}
85
102
  <nav aria-label={navLabel} className="flex flex-col gap-1 px-2 pt-2">
86
103
  {navLinks}
87
104
  </nav>
@@ -92,10 +109,14 @@ export function SuiteHeader({
92
109
  </SiteHeaderBrand>
93
110
  <SiteNav aria-label={navLabel}>{navLinks}</SiteNav>
94
111
  <SiteHeaderActions>
95
- {search}
112
+ {search ? (
113
+ <SearchRelay handler={openSearch}>
114
+ <div className="hidden md:contents">{search}</div>
115
+ </SearchRelay>
116
+ ) : null}
96
117
  <Separator
97
118
  orientation="vertical"
98
- className="mx-1 hidden data-vertical:h-5 data-vertical:self-center sm:block"
119
+ className="mx-1 hidden data-vertical:h-5 data-vertical:self-center md:block"
99
120
  />
100
121
  {themeMenu ?? <ThemeMenu />}
101
122
  {repository ? (
@@ -187,6 +187,62 @@
187
187
  translate: 250% 0;
188
188
  }
189
189
  }
190
+ /* Spinner variants (spinner.tsx) and segmented Progress: cells light up and fade, a glyph strip steps (braille), a cursor
191
+ blinks, equalizer bars bounce, a scan line sweeps. Delays are set per cell. */
192
+ --animate-loader-cell: kk-loader-cell 1.2s ease-in-out infinite;
193
+ @keyframes kk-loader-cell {
194
+ 0%,
195
+ 100% {
196
+ opacity: 0.2;
197
+ }
198
+ 30% {
199
+ opacity: 1;
200
+ }
201
+ 60% {
202
+ opacity: 0.2;
203
+ }
204
+ }
205
+ --animate-loader-braille: kk-loader-braille 0.8s steps(10) infinite;
206
+ @keyframes kk-loader-braille {
207
+ to {
208
+ translate: 0 -100%;
209
+ }
210
+ }
211
+ --animate-loader-blink: kk-loader-blink 1s step-end infinite;
212
+ @keyframes kk-loader-blink {
213
+ 50% {
214
+ opacity: 0;
215
+ }
216
+ }
217
+ --animate-loader-bars: kk-loader-bars 0.9s ease-in-out infinite;
218
+ @keyframes kk-loader-bars {
219
+ 0%,
220
+ 100% {
221
+ scale: 1 0.3;
222
+ }
223
+ 50% {
224
+ scale: 1 1;
225
+ }
226
+ }
227
+ --animate-loader-scan: kk-loader-scan 1.4s linear infinite;
228
+ @keyframes kk-loader-scan {
229
+ from {
230
+ translate: -100% 0;
231
+ }
232
+ to {
233
+ translate: 100% 0;
234
+ }
235
+ }
236
+ /* Shimmer: a highlight band sweeping across clipped text. */
237
+ --animate-shimmer: kk-shimmer 2s linear infinite;
238
+ @keyframes kk-shimmer {
239
+ from {
240
+ background-position: 100% 0;
241
+ }
242
+ to {
243
+ background-position: 0% 0;
244
+ }
245
+ }
190
246
  /* Boxes under 20px (checkbox, kbd keys, tiny chips): capped so they never turn into circles at xl. */
191
247
  --radius-box: min(calc(var(--kk-radius) * 0.5), 0.25rem);
192
248