@krizaka/ui 3.36.0 → 3.38.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 (115) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/activity-feed.js.map +1 -1
  3. package/dist/app-shell.js +3 -3
  4. package/dist/app-shell.js.map +1 -1
  5. package/dist/badge.d.ts +1 -1
  6. package/dist/badge.js +1 -1
  7. package/dist/badge.js.map +1 -1
  8. package/dist/blocks.css +30 -30
  9. package/dist/card.d.ts +6 -6
  10. package/dist/card.js +2 -2
  11. package/dist/card.js.map +1 -1
  12. package/dist/chip-row.js +1 -1
  13. package/dist/chip-row.js.map +1 -1
  14. package/dist/chip.js +1 -1
  15. package/dist/chip.js.map +1 -1
  16. package/dist/{chunk-QDXV544C.js → chunk-4QH6CP2Y.js} +2 -2
  17. package/dist/chunk-4QH6CP2Y.js.map +1 -0
  18. package/dist/{chunk-CKSHLSDH.js → chunk-AAP3U66V.js} +2 -2
  19. package/dist/{chunk-7UCPGXKN.js → chunk-C3NF7V5Y.js} +2 -2
  20. package/dist/{chunk-7UCPGXKN.js.map → chunk-C3NF7V5Y.js.map} +1 -1
  21. package/dist/{chunk-K2ZYB4HP.js → chunk-S5LP7KKW.js} +2 -2
  22. package/dist/{chunk-63KCDMX2.js → chunk-VPO27AFS.js} +4 -4
  23. package/dist/chunk-VPO27AFS.js.map +1 -0
  24. package/dist/{chunk-YOTQD7JD.js → chunk-WYQ277F2.js} +2 -2
  25. package/dist/{chunk-2DGLWTPS.js → chunk-YKOLME7R.js} +3 -3
  26. package/dist/{chunk-2DGLWTPS.js.map → chunk-YKOLME7R.js.map} +1 -1
  27. package/dist/code-block.js +4 -4
  28. package/dist/code-block.js.map +1 -1
  29. package/dist/combobox.js +2 -2
  30. package/dist/data-table.js +3 -3
  31. package/dist/file-upload.js +3 -3
  32. package/dist/file-upload.js.map +1 -1
  33. package/dist/filter-bar.js +2 -2
  34. package/dist/genui.js +4 -4
  35. package/dist/landing-page.js +1 -6
  36. package/dist/landing-page.js.map +1 -1
  37. package/dist/language-switch.js +1 -1
  38. package/dist/marketing-layout.js +1 -1
  39. package/dist/marketing-layout.js.map +1 -1
  40. package/dist/media-grid.js +1 -1
  41. package/dist/meter.js +2 -2
  42. package/dist/meter.js.map +1 -1
  43. package/dist/motion.css +2 -2
  44. package/dist/native.js +4 -4
  45. package/dist/native.js.map +1 -1
  46. package/dist/nav-bar.js +7 -7
  47. package/dist/nav-bar.js.map +1 -1
  48. package/dist/product-page.js +1 -6
  49. package/dist/product-page.js.map +1 -1
  50. package/dist/progress.js +3 -3
  51. package/dist/progress.js.map +1 -1
  52. package/dist/rail-nav.js +1 -1
  53. package/dist/rail-nav.js.map +1 -1
  54. package/dist/service-card.js +2 -2
  55. package/dist/service-card.js.map +1 -1
  56. package/dist/settings-layout.js +1 -1
  57. package/dist/settings-layout.js.map +1 -1
  58. package/dist/site-footer.js +2 -2
  59. package/dist/site-footer.js.map +1 -1
  60. package/dist/skip-link.js +1 -1
  61. package/dist/slot-picker.js +1 -1
  62. package/dist/slot-picker.js.map +1 -1
  63. package/dist/step-progress.js +1 -1
  64. package/dist/step-progress.js.map +1 -1
  65. package/dist/tabs.js +4 -4
  66. package/dist/tabs.js.map +1 -1
  67. package/dist/tag-input.js +2 -2
  68. package/dist/tag-selector.js +1 -1
  69. package/dist/toast.js +1 -1
  70. package/dist/toast.js.map +1 -1
  71. package/package.json +5 -5
  72. package/registry/activity-feed.json +1 -1
  73. package/registry/app-shell.json +1 -1
  74. package/registry/badge.json +1 -1
  75. package/registry/card.json +1 -1
  76. package/registry/chip-row.json +1 -1
  77. package/registry/chip.json +1 -1
  78. package/registry/examples/badge/native/dot.dark.png +0 -0
  79. package/registry/examples/badge/native/dot.light.png +0 -0
  80. package/registry/examples/badge/native/scrim.dark.png +0 -0
  81. package/registry/examples/badge/native/scrim.light.png +0 -0
  82. package/registry/examples/badge/native/tones.dark.png +0 -0
  83. package/registry/examples/badge/native/tones.light.png +0 -0
  84. package/registry/examples/card/native/default.dark.png +0 -0
  85. package/registry/examples/card/native/default.light.png +0 -0
  86. package/registry/examples/card/native/elevated.dark.png +0 -0
  87. package/registry/examples/card/native/elevated.light.png +0 -0
  88. package/registry/examples/description-list/native/job-details.dark.png +0 -0
  89. package/registry/examples/description-list/native/job-details.light.png +0 -0
  90. package/registry/examples/meta-row/native/video.dark.png +0 -0
  91. package/registry/examples/meta-row/native/video.light.png +0 -0
  92. package/registry/examples/swipe-deck/native/discover.dark.png +0 -0
  93. package/registry/examples/swipe-deck/native/discover.light.png +0 -0
  94. package/registry/index.json +5 -5
  95. package/registry/landing-page.json +1 -1
  96. package/registry/language-switch.json +1 -1
  97. package/registry/map.json +1 -1
  98. package/registry/meter.json +1 -1
  99. package/registry/nav-bar.json +2 -2
  100. package/registry/product-page.json +1 -1
  101. package/registry/progress.json +1 -1
  102. package/registry/rail-nav.json +2 -2
  103. package/registry/service-card.json +1 -1
  104. package/registry/settings-layout.json +1 -1
  105. package/registry/site-footer.json +2 -2
  106. package/registry/skip-link.json +1 -1
  107. package/registry/slot-picker.json +1 -1
  108. package/registry/step-progress.json +1 -1
  109. package/registry/tabs.json +1 -1
  110. package/registry/toast.json +1 -1
  111. package/dist/chunk-63KCDMX2.js.map +0 -1
  112. package/dist/chunk-QDXV544C.js.map +0 -1
  113. /package/dist/{chunk-CKSHLSDH.js.map → chunk-AAP3U66V.js.map} +0 -0
  114. /package/dist/{chunk-K2ZYB4HP.js.map → chunk-S5LP7KKW.js.map} +0 -0
  115. /package/dist/{chunk-YOTQD7JD.js.map → chunk-WYQ277F2.js.map} +0 -0
@@ -61,7 +61,7 @@ var activityFeed = tv({
61
61
  rail: "absolute left-[4.5px] top-5 -bottom-1 w-px bg-border-default",
62
62
  body: "flex min-w-0 flex-1 flex-col",
63
63
  title: "text-sm text-fg",
64
- count: "ml-1.5 inline-flex rounded-full bg-surface-3 px-1.5 text-xs font-semibold tabular-nums text-fg-secondary",
64
+ count: "ml-1.5 inline-flex rounded-sm bg-surface-3 px-1.5 text-xs font-semibold tabular-nums text-fg-secondary",
65
65
  description: "text-xs text-fg-secondary",
66
66
  time: "shrink-0 pt-0.5 text-xs tabular-nums text-fg-secondary",
67
67
  leading: "shrink-0"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/activity-feed/activity-feed.tsx","../src/activity-feed/group.ts"],"sourcesContent":["// Client (the clock of “… ago”, the announcement of new events). What happened, newest first: an activity feed\n// grouped by day with folded runs and relative times, announced politely when it is live; and a `Timeline` — one\n// thing's history on a rail, oldest first. Words are props.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { type ActivityEvent, type ActivityTone, groupActivity, timeAgo } from \"./group\";\n\nexport const activityFeed = tv({\n slots: {\n root: \"flex flex-col gap-5\",\n group: \"flex flex-col gap-1\",\n heading: \"sticky top-0 z-10 bg-surface-0 py-1 text-xs font-semibold text-fg-secondary\",\n ol: \"m-0 flex list-none flex-col p-0\",\n item: \"relative flex gap-3 py-2.5\",\n marker: \"relative mt-1 flex h-2.5 w-2.5 shrink-0 rounded-full bg-[var(--kz-af-tone)] ring-4 ring-surface-0\",\n rail: \"absolute left-[4.5px] top-5 -bottom-1 w-px bg-border-default\",\n body: \"flex min-w-0 flex-1 flex-col\",\n title: \"text-sm text-fg\",\n count: \"ml-1.5 inline-flex rounded-full bg-surface-3 px-1.5 text-xs font-semibold tabular-nums text-fg-secondary\",\n description: \"text-xs text-fg-secondary\",\n time: \"shrink-0 pt-0.5 text-xs tabular-nums text-fg-secondary\",\n leading: \"shrink-0\",\n },\n});\n\nconst TONE: Record<ActivityTone, string> = {\n neutral: \"var(--kz-border-strong)\",\n accent: \"var(--kz-accent)\",\n success: \"var(--kz-success)\",\n warning: \"var(--kz-warning)\",\n danger: \"var(--kz-danger)\",\n info: \"var(--kz-info)\",\n};\n\nexport type ActivityFeedLabels = {\n /** The heading of today's events. */\n today: string;\n /** The heading of yesterday's. */\n yesterday: string;\n /** A folded run: `(n) => \"×\" + n` (“3 times”). */\n times: (count: number) => string;\n /** Announced when new events arrive in a live feed: `(n) => n + \" new events\"`. */\n newEvents: (count: number) => string;\n};\n\nexport const activityFeedLabels: ActivityFeedLabels = {\n today: \"Today\",\n yesterday: \"Yesterday\",\n times: (count) => `×${count}`,\n newEvents: (count) => (count === 1 ? \"1 new event\" : `${count} new events`),\n};\n\nexport type ActivityFeedProps<T extends ActivityEvent> = {\n /** The events, in any order. */\n events: readonly T[];\n /** The feed's accessible name (“Activity”). */\n \"aria-label\": string;\n /** The locale of the dates and the “… ago”. Default `en-US`. */\n locale?: string;\n /** Now, for the “… ago” and the day headings (pass it from the server for the same text on both sides). */\n now?: Date;\n /** Refresh the “… ago” every minute. Default true. */\n ticking?: boolean;\n /** Fold the runs of the same actor and kind. Default true. */\n fold?: boolean;\n /** Announce new events politely (a stream). */\n live?: boolean;\n /** Before an event's text: its actor's `Avatar`, an icon. */\n leading?: (event: T) => React.ReactNode;\n /** After an event's text: an action (Reply, View). */\n action?: (event: T) => React.ReactNode;\n /** The words, passed translated; English by default. */\n labels?: Partial<ActivityFeedLabels>;\n /** Shown when there is no event (an `EmptyState`). */\n empty?: React.ReactNode;\n /** Classes merged on the root. */\n className?: string;\n};\n\nfunction useNow(initial: Date | undefined, ticking: boolean) {\n const [now, setNow] = React.useState(() => initial ?? new Date());\n React.useEffect(() => {\n if (!ticking) return;\n const timer = setInterval(() => setNow(new Date()), 60_000);\n return () => clearInterval(timer);\n }, [ticking]);\n return now;\n}\n\n/**\n * What happened, newest first: grouped by day, runs folded (“Mina tipped ×3”), “2 min ago” kept fresh, new events\n * announced when `live`. Orazaka's job activity, Orochia's notifications, the console's audit log.\n */\nexport function ActivityFeed<T extends ActivityEvent>({ events, \"aria-label\": label, locale = \"en-US\", now: given, ticking = true, fold = true, live, leading, action, labels: words, empty, className }: ActivityFeedProps<T>) {\n const labels = { ...activityFeedLabels, ...words };\n const s = activityFeed();\n const now = useNow(given, ticking && !given);\n const groups = groupActivity(events, { now, locale, today: labels.today, yesterday: labels.yesterday, fold });\n const seen = React.useRef(events.length);\n const [announcement, setAnnouncement] = React.useState(\"\");\n React.useEffect(() => {\n const added = events.length - seen.current;\n seen.current = events.length;\n if (!live || added <= 0) return;\n const timer = setTimeout(() => setAnnouncement(labels.newEvents(added)), 0);\n return () => clearTimeout(timer);\n }, [events.length, live]); // eslint-disable-line react-hooks/exhaustive-deps -- announce on a new count only\n return (\n <section aria-label={label} className={s.root({ className })}>\n {live ? (\n <p className=\"sr-only\" aria-live=\"polite\">\n {announcement}\n </p>\n ) : null}\n {groups.length === 0 && empty ? empty : null}\n {groups.map((group) => (\n <div key={group.label} className={s.group()}>\n <h3 className={s.heading()}>{group.label}</h3>\n <ol className={s.ol()}>\n {group.events.map(({ event, count }) => {\n const date = new Date(event.at);\n return (\n <li key={event.id} className={s.item()} style={{ [\"--kz-af-tone\" as string]: TONE[event.tone ?? \"neutral\"] }}>\n {leading ? <span className={s.leading()}>{leading(event)}</span> : <span aria-hidden className={s.marker()} />}\n <span className={s.body()}>\n <span className={s.title()}>\n {event.title}\n {count > 1 ? <span className={s.count()}>{labels.times(count)}</span> : null}\n </span>\n {event.description ? <span className={s.description()}>{event.description}</span> : null}\n </span>\n <time dateTime={date.toISOString()} title={date.toLocaleString(locale)} className={s.time()}>\n {timeAgo(date, { now, locale })}\n </time>\n {action ? <span className=\"shrink-0\">{action(event)}</span> : null}\n </li>\n );\n })}\n </ol>\n </div>\n ))}\n </section>\n );\n}\n\nexport type TimelineProps<T extends ActivityEvent> = {\n /** One thing's events, oldest first (a job's run, a payout's life). */\n events: readonly T[];\n /** The timeline's accessible name (“History of the payout”). */\n \"aria-label\": string;\n /** The locale of the times. Default `en-US`. */\n locale?: string;\n /** How a time is written: `time` (14:02, default), `datetime`, `date`. */\n timeStyle?: \"time\" | \"datetime\" | \"date\";\n /** Classes merged on the list. */\n className?: string;\n};\n\n/** One thing's history on a rail, oldest first: each event a marker in its tone, its time and its words. */\nexport function Timeline<T extends ActivityEvent>({ events, \"aria-label\": label, locale = \"en-US\", timeStyle = \"time\", className }: TimelineProps<T>) {\n const s = activityFeed();\n const options: Intl.DateTimeFormatOptions =\n timeStyle === \"time\" ? { hour: \"2-digit\", minute: \"2-digit\" } : timeStyle === \"date\" ? { dateStyle: \"medium\" } : { dateStyle: \"medium\", timeStyle: \"short\" };\n const write = new Intl.DateTimeFormat(locale, options);\n return (\n <ol aria-label={label} className={s.ol({ className })}>\n {events.map((event, i) => {\n const date = new Date(event.at);\n return (\n <li key={event.id} className={s.item()} style={{ [\"--kz-af-tone\" as string]: TONE[event.tone ?? \"neutral\"] }}>\n <span aria-hidden className={s.marker()} />\n {i < events.length - 1 ? <span aria-hidden className={s.rail()} /> : null}\n <span className={s.body()}>\n <span className={s.title()}>{event.title}</span>\n {event.description ? <span className={s.description()}>{event.description}</span> : null}\n </span>\n <time dateTime={date.toISOString()} className={s.time()}>\n {write.format(date)}\n </time>\n </li>\n );\n })}\n </ol>\n );\n}\n","// Shared by the web and React Native ActivityFeed: the events grouped by day (“Today”, “Yesterday”, a date), the same\n// events of one actor in a row folded into one (“Mina tipped 3 times”), and how long ago each happened — Intl only.\n\nexport type ActivityTone = \"neutral\" | \"accent\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nexport type ActivityEvent = {\n /** A stable id. */\n id: string;\n /** When it happened: an ISO date-time, an epoch in ms or a `Date`. */\n at: string | number | Date;\n /** What happened, in a sentence — passed translated (“Mina tipped your video $5”). */\n title: string;\n /** A detail under it. */\n description?: string;\n /** Who did it (folds a run of the same `kind` by the same actor). */\n actor?: string;\n /** The kind of event (`tip`, `bid`, `job.done`): with `actor`, what folds. */\n kind?: string;\n /** The tone of its marker: success for a payout, danger for a failure. */\n tone?: ActivityTone;\n};\n\nexport type ActivityGroup<T extends ActivityEvent> = {\n /** The day's heading (“Today”, “Yesterday”, “Monday, Oct 6”). */\n label: string;\n /** Its events, newest first; a folded run is its newest event with `count` > 1. */\n events: { event: T; count: number }[];\n};\n\nconst dayKey = (date: Date) => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;\n\n/** Groups events (any order) by day, newest first, folding runs of the same actor and kind when `fold` is set. */\nexport function groupActivity<T extends ActivityEvent>(\n events: readonly T[],\n { now = new Date(), locale = \"en-US\", today = \"Today\", yesterday = \"Yesterday\", fold = true }: { now?: Date; locale?: string; today?: string; yesterday?: string; fold?: boolean } = {},\n): ActivityGroup<T>[] {\n const sorted = [...events].sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime());\n const day = new Intl.DateTimeFormat(locale, { weekday: \"long\", month: \"short\", day: \"numeric\" });\n const todayKey = dayKey(now);\n const yesterdayKey = dayKey(new Date(now.getTime() - 86_400_000));\n const groups: ActivityGroup<T>[] = [];\n let current: ActivityGroup<T> | null = null;\n let currentKey = \"\";\n for (const event of sorted) {\n const date = new Date(event.at);\n const key = dayKey(date);\n if (!current || key !== currentKey) {\n currentKey = key;\n current = { label: key === todayKey ? today : key === yesterdayKey ? yesterday : day.format(date), events: [] };\n groups.push(current);\n }\n const last = current.events[current.events.length - 1];\n if (fold && last && event.actor && event.kind && last.event.actor === event.actor && last.event.kind === event.kind) last.count += 1;\n else current.events.push({ event, count: 1 });\n }\n return groups;\n}\n\nconst UNITS: [Intl.RelativeTimeFormatUnit, number][] = [\n [\"second\", 60],\n [\"minute\", 60],\n [\"hour\", 24],\n [\"day\", 7],\n [\"week\", 4.35],\n [\"month\", 12],\n [\"year\", Number.POSITIVE_INFINITY],\n];\n\n/** “2 minutes ago”, “yesterday”: how long ago, in the largest unit that reads well (`style` short: “2 min. ago”). */\nexport function timeAgo(at: string | number | Date, { now = new Date(), locale = \"en-US\", style = \"short\" }: { now?: Date; locale?: string; style?: \"long\" | \"short\" | \"narrow\" } = {}): string {\n let value = (new Date(at).getTime() - now.getTime()) / 1000;\n let unit: Intl.RelativeTimeFormatUnit = \"second\";\n for (const [name, size] of UNITS) {\n unit = name;\n if (Math.abs(value) < size - 0.5) break;\n value /= size;\n }\n return new Intl.RelativeTimeFormat(locale, { numeric: \"auto\", style }).format(Math.round(value) + 0, unit);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/activity-feed/activity-feed.tsx","../src/activity-feed/group.ts"],"sourcesContent":["// Client (the clock of “… ago”, the announcement of new events). What happened, newest first: an activity feed\n// grouped by day with folded runs and relative times, announced politely when it is live; and a `Timeline` — one\n// thing's history on a rail, oldest first. Words are props.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { type ActivityEvent, type ActivityTone, groupActivity, timeAgo } from \"./group\";\n\nexport const activityFeed = tv({\n slots: {\n root: \"flex flex-col gap-5\",\n group: \"flex flex-col gap-1\",\n heading: \"sticky top-0 z-10 bg-surface-0 py-1 text-xs font-semibold text-fg-secondary\",\n ol: \"m-0 flex list-none flex-col p-0\",\n item: \"relative flex gap-3 py-2.5\",\n marker: \"relative mt-1 flex h-2.5 w-2.5 shrink-0 rounded-full bg-[var(--kz-af-tone)] ring-4 ring-surface-0\",\n rail: \"absolute left-[4.5px] top-5 -bottom-1 w-px bg-border-default\",\n body: \"flex min-w-0 flex-1 flex-col\",\n title: \"text-sm text-fg\",\n count: \"ml-1.5 inline-flex rounded-sm bg-surface-3 px-1.5 text-xs font-semibold tabular-nums text-fg-secondary\",\n description: \"text-xs text-fg-secondary\",\n time: \"shrink-0 pt-0.5 text-xs tabular-nums text-fg-secondary\",\n leading: \"shrink-0\",\n },\n});\n\nconst TONE: Record<ActivityTone, string> = {\n neutral: \"var(--kz-border-strong)\",\n accent: \"var(--kz-accent)\",\n success: \"var(--kz-success)\",\n warning: \"var(--kz-warning)\",\n danger: \"var(--kz-danger)\",\n info: \"var(--kz-info)\",\n};\n\nexport type ActivityFeedLabels = {\n /** The heading of today's events. */\n today: string;\n /** The heading of yesterday's. */\n yesterday: string;\n /** A folded run: `(n) => \"×\" + n` (“3 times”). */\n times: (count: number) => string;\n /** Announced when new events arrive in a live feed: `(n) => n + \" new events\"`. */\n newEvents: (count: number) => string;\n};\n\nexport const activityFeedLabels: ActivityFeedLabels = {\n today: \"Today\",\n yesterday: \"Yesterday\",\n times: (count) => `×${count}`,\n newEvents: (count) => (count === 1 ? \"1 new event\" : `${count} new events`),\n};\n\nexport type ActivityFeedProps<T extends ActivityEvent> = {\n /** The events, in any order. */\n events: readonly T[];\n /** The feed's accessible name (“Activity”). */\n \"aria-label\": string;\n /** The locale of the dates and the “… ago”. Default `en-US`. */\n locale?: string;\n /** Now, for the “… ago” and the day headings (pass it from the server for the same text on both sides). */\n now?: Date;\n /** Refresh the “… ago” every minute. Default true. */\n ticking?: boolean;\n /** Fold the runs of the same actor and kind. Default true. */\n fold?: boolean;\n /** Announce new events politely (a stream). */\n live?: boolean;\n /** Before an event's text: its actor's `Avatar`, an icon. */\n leading?: (event: T) => React.ReactNode;\n /** After an event's text: an action (Reply, View). */\n action?: (event: T) => React.ReactNode;\n /** The words, passed translated; English by default. */\n labels?: Partial<ActivityFeedLabels>;\n /** Shown when there is no event (an `EmptyState`). */\n empty?: React.ReactNode;\n /** Classes merged on the root. */\n className?: string;\n};\n\nfunction useNow(initial: Date | undefined, ticking: boolean) {\n const [now, setNow] = React.useState(() => initial ?? new Date());\n React.useEffect(() => {\n if (!ticking) return;\n const timer = setInterval(() => setNow(new Date()), 60_000);\n return () => clearInterval(timer);\n }, [ticking]);\n return now;\n}\n\n/**\n * What happened, newest first: grouped by day, runs folded (“Mina tipped ×3”), “2 min ago” kept fresh, new events\n * announced when `live`. Orazaka's job activity, Orochia's notifications, the console's audit log.\n */\nexport function ActivityFeed<T extends ActivityEvent>({ events, \"aria-label\": label, locale = \"en-US\", now: given, ticking = true, fold = true, live, leading, action, labels: words, empty, className }: ActivityFeedProps<T>) {\n const labels = { ...activityFeedLabels, ...words };\n const s = activityFeed();\n const now = useNow(given, ticking && !given);\n const groups = groupActivity(events, { now, locale, today: labels.today, yesterday: labels.yesterday, fold });\n const seen = React.useRef(events.length);\n const [announcement, setAnnouncement] = React.useState(\"\");\n React.useEffect(() => {\n const added = events.length - seen.current;\n seen.current = events.length;\n if (!live || added <= 0) return;\n const timer = setTimeout(() => setAnnouncement(labels.newEvents(added)), 0);\n return () => clearTimeout(timer);\n }, [events.length, live]); // eslint-disable-line react-hooks/exhaustive-deps -- announce on a new count only\n return (\n <section aria-label={label} className={s.root({ className })}>\n {live ? (\n <p className=\"sr-only\" aria-live=\"polite\">\n {announcement}\n </p>\n ) : null}\n {groups.length === 0 && empty ? empty : null}\n {groups.map((group) => (\n <div key={group.label} className={s.group()}>\n <h3 className={s.heading()}>{group.label}</h3>\n <ol className={s.ol()}>\n {group.events.map(({ event, count }) => {\n const date = new Date(event.at);\n return (\n <li key={event.id} className={s.item()} style={{ [\"--kz-af-tone\" as string]: TONE[event.tone ?? \"neutral\"] }}>\n {leading ? <span className={s.leading()}>{leading(event)}</span> : <span aria-hidden className={s.marker()} />}\n <span className={s.body()}>\n <span className={s.title()}>\n {event.title}\n {count > 1 ? <span className={s.count()}>{labels.times(count)}</span> : null}\n </span>\n {event.description ? <span className={s.description()}>{event.description}</span> : null}\n </span>\n <time dateTime={date.toISOString()} title={date.toLocaleString(locale)} className={s.time()}>\n {timeAgo(date, { now, locale })}\n </time>\n {action ? <span className=\"shrink-0\">{action(event)}</span> : null}\n </li>\n );\n })}\n </ol>\n </div>\n ))}\n </section>\n );\n}\n\nexport type TimelineProps<T extends ActivityEvent> = {\n /** One thing's events, oldest first (a job's run, a payout's life). */\n events: readonly T[];\n /** The timeline's accessible name (“History of the payout”). */\n \"aria-label\": string;\n /** The locale of the times. Default `en-US`. */\n locale?: string;\n /** How a time is written: `time` (14:02, default), `datetime`, `date`. */\n timeStyle?: \"time\" | \"datetime\" | \"date\";\n /** Classes merged on the list. */\n className?: string;\n};\n\n/** One thing's history on a rail, oldest first: each event a marker in its tone, its time and its words. */\nexport function Timeline<T extends ActivityEvent>({ events, \"aria-label\": label, locale = \"en-US\", timeStyle = \"time\", className }: TimelineProps<T>) {\n const s = activityFeed();\n const options: Intl.DateTimeFormatOptions =\n timeStyle === \"time\" ? { hour: \"2-digit\", minute: \"2-digit\" } : timeStyle === \"date\" ? { dateStyle: \"medium\" } : { dateStyle: \"medium\", timeStyle: \"short\" };\n const write = new Intl.DateTimeFormat(locale, options);\n return (\n <ol aria-label={label} className={s.ol({ className })}>\n {events.map((event, i) => {\n const date = new Date(event.at);\n return (\n <li key={event.id} className={s.item()} style={{ [\"--kz-af-tone\" as string]: TONE[event.tone ?? \"neutral\"] }}>\n <span aria-hidden className={s.marker()} />\n {i < events.length - 1 ? <span aria-hidden className={s.rail()} /> : null}\n <span className={s.body()}>\n <span className={s.title()}>{event.title}</span>\n {event.description ? <span className={s.description()}>{event.description}</span> : null}\n </span>\n <time dateTime={date.toISOString()} className={s.time()}>\n {write.format(date)}\n </time>\n </li>\n );\n })}\n </ol>\n );\n}\n","// Shared by the web and React Native ActivityFeed: the events grouped by day (“Today”, “Yesterday”, a date), the same\n// events of one actor in a row folded into one (“Mina tipped 3 times”), and how long ago each happened — Intl only.\n\nexport type ActivityTone = \"neutral\" | \"accent\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nexport type ActivityEvent = {\n /** A stable id. */\n id: string;\n /** When it happened: an ISO date-time, an epoch in ms or a `Date`. */\n at: string | number | Date;\n /** What happened, in a sentence — passed translated (“Mina tipped your video $5”). */\n title: string;\n /** A detail under it. */\n description?: string;\n /** Who did it (folds a run of the same `kind` by the same actor). */\n actor?: string;\n /** The kind of event (`tip`, `bid`, `job.done`): with `actor`, what folds. */\n kind?: string;\n /** The tone of its marker: success for a payout, danger for a failure. */\n tone?: ActivityTone;\n};\n\nexport type ActivityGroup<T extends ActivityEvent> = {\n /** The day's heading (“Today”, “Yesterday”, “Monday, Oct 6”). */\n label: string;\n /** Its events, newest first; a folded run is its newest event with `count` > 1. */\n events: { event: T; count: number }[];\n};\n\nconst dayKey = (date: Date) => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;\n\n/** Groups events (any order) by day, newest first, folding runs of the same actor and kind when `fold` is set. */\nexport function groupActivity<T extends ActivityEvent>(\n events: readonly T[],\n { now = new Date(), locale = \"en-US\", today = \"Today\", yesterday = \"Yesterday\", fold = true }: { now?: Date; locale?: string; today?: string; yesterday?: string; fold?: boolean } = {},\n): ActivityGroup<T>[] {\n const sorted = [...events].sort((a, b) => new Date(b.at).getTime() - new Date(a.at).getTime());\n const day = new Intl.DateTimeFormat(locale, { weekday: \"long\", month: \"short\", day: \"numeric\" });\n const todayKey = dayKey(now);\n const yesterdayKey = dayKey(new Date(now.getTime() - 86_400_000));\n const groups: ActivityGroup<T>[] = [];\n let current: ActivityGroup<T> | null = null;\n let currentKey = \"\";\n for (const event of sorted) {\n const date = new Date(event.at);\n const key = dayKey(date);\n if (!current || key !== currentKey) {\n currentKey = key;\n current = { label: key === todayKey ? today : key === yesterdayKey ? yesterday : day.format(date), events: [] };\n groups.push(current);\n }\n const last = current.events[current.events.length - 1];\n if (fold && last && event.actor && event.kind && last.event.actor === event.actor && last.event.kind === event.kind) last.count += 1;\n else current.events.push({ event, count: 1 });\n }\n return groups;\n}\n\nconst UNITS: [Intl.RelativeTimeFormatUnit, number][] = [\n [\"second\", 60],\n [\"minute\", 60],\n [\"hour\", 24],\n [\"day\", 7],\n [\"week\", 4.35],\n [\"month\", 12],\n [\"year\", Number.POSITIVE_INFINITY],\n];\n\n/** “2 minutes ago”, “yesterday”: how long ago, in the largest unit that reads well (`style` short: “2 min. ago”). */\nexport function timeAgo(at: string | number | Date, { now = new Date(), locale = \"en-US\", style = \"short\" }: { now?: Date; locale?: string; style?: \"long\" | \"short\" | \"narrow\" } = {}): string {\n let value = (new Date(at).getTime() - now.getTime()) / 1000;\n let unit: Intl.RelativeTimeFormatUnit = \"second\";\n for (const [name, size] of UNITS) {\n unit = name;\n if (Math.abs(value) < size - 0.5) break;\n value /= size;\n }\n return new Intl.RelativeTimeFormat(locale, { numeric: \"auto\", style }).format(Math.round(value) + 0, unit);\n}\n"],"mappings":"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","names":[]}
package/dist/app-shell.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  SkipLink
4
- } from "./chunk-QDXV544C.js";
4
+ } from "./chunk-4QH6CP2Y.js";
5
5
  import {
6
6
  ChevronGlyph,
7
7
  CloseGlyph,
@@ -137,10 +137,10 @@ var appShell = tv2({
137
137
  brand: "flex h-14 shrink-0 items-center gap-2.5 px-4 font-display text-[15px] font-bold tracking-tight [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [[data-desktop]_&]:h-11",
138
138
  nav: "flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2",
139
139
  group: "mt-3 mb-1 px-3 font-mono text-[10.5px] font-semibold text-fg-secondary [[data-collapsed]_&]:sr-only",
140
- item: "group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-full aria-[current=page]:before:bg-accent [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0 [[data-desktop]_&]:h-8 [[data-desktop]_&]:cursor-default [[data-desktop]_&]:rounded-md [[data-desktop]_&]:text-[13px] [[data-desktop]_&_svg]:h-4 [[data-desktop]_&_svg]:w-4",
140
+ item: "group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-sm aria-[current=page]:before:bg-accent [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0 [[data-desktop]_&]:h-8 [[data-desktop]_&]:cursor-default [[data-desktop]_&]:rounded-md [[data-desktop]_&]:text-[13px] [[data-desktop]_&_svg]:h-4 [[data-desktop]_&_svg]:w-4",
141
141
  itemLabel: "min-w-0 flex-1 truncate [[data-collapsed]_&]:sr-only",
142
142
  itemHint: "ml-auto hidden items-center gap-0.5 text-fg-secondary group-hover/item:flex group-focus-visible/item:flex [[data-collapsed]_&]:!hidden",
143
- itemBadge: "ml-auto inline-flex min-w-5 items-center justify-center rounded-full bg-accent px-1.5 text-[11px] font-bold text-on-accent [[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]",
143
+ itemBadge: "ml-auto inline-flex min-w-5 items-center justify-center rounded-sm bg-accent px-1.5 text-[11px] font-bold text-on-accent [[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]",
144
144
  footer: "shrink-0 border-t border-border-subtle p-2",
145
145
  collapse: "hidden h-9 w-full items-center gap-2 rounded-lg px-3 text-xs font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg md:flex focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0",
146
146
  body: "flex min-w-0 flex-1 flex-col [[data-desktop]_&]:min-h-0 [[data-desktop]_&]:overflow-y-auto",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/app-shell/app-shell.tsx","../src/kbd/kbd.tsx","../src/app-shell/shortcuts.ts","../src/media.ts"],"sourcesContent":["// Client (the collapse, the drawer, the shortcuts). The shell of an application screen, read from the products' consoles\n// (Orochia admin and studio, Orazaka web-admin and web-client): a sidebar of sections that collapses to its icons on a\n// desktop, a header with the screen's title, a search that opens the app's command palette and its actions, the content —\n// and below `md` the sidebar becomes a drawer opened from the header (Radix Dialog: focus trap, Escape, focus return).\n// Keyboard: ⌘/Ctrl+B folds the sidebar, ⌘/Ctrl+K opens the search, `g` then a letter goes to a section (`shortcut` of an\n// item). Words arrive as props; links through `asChild`.\n// `desktop` (ADR-002): the shell is the window of a desktop app (the Orazaka Mac app, Tauri 2): the sidebar is always\n// there (a window has a minimum width, no drawer), its top clears the traffic lights, the header and the sidebar's top are\n// the title bar (`data-tauri-drag-region`: dragging them moves the window), the density is the desktop's (32 px rows,\n// 13 px text, a 48 px header), the chrome is not selectable, and the content scrolls under a fixed header.\nimport { SkipLink } from \"@krizaka/ui/skip-link\";\nimport { Dialog as DialogPrimitive, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { ChevronGlyph, CloseGlyph, SearchGlyph } from \"../glyphs\";\nimport { Kbd } from \"../kbd\";\nimport { ariaKeyShortcuts, formatShortcut, useApple, useShortcuts } from \"./shortcuts\";\n\nexport const appShell = tv({\n slots: {\n root: \"flex min-h-dvh w-full bg-surface-0 text-fg data-desktop:h-[var(--kz-window-height,100dvh)] data-desktop:overflow-hidden\",\n sidebar:\n \"sticky top-0 hidden h-dvh shrink-0 flex-col border-r border-border-subtle bg-surface-1 transition-[width] duration-300 motion-reduce:transition-none md:flex \" +\n \"w-64 data-collapsed:w-[4.25rem] \" +\n \"[[data-desktop]_&]:static [[data-desktop]_&]:flex [[data-desktop]_&]:h-auto [[data-desktop]_&]:w-60 [[data-desktop]_&]:select-none [[data-desktop]_&]:data-collapsed:w-[4.75rem]\",\n titlebar: \"h-[var(--kz-titlebar-inset,1.75rem)] shrink-0\",\n drawer:\n \"kz-dialog fixed inset-y-0 left-0 z-50 flex w-[min(20rem,88vw)] flex-col border-r border-border-default bg-surface-1 pl-[env(safe-area-inset-left)] shadow-lg focus:outline-hidden\",\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay\",\n brand: \"flex h-14 shrink-0 items-center gap-2.5 px-4 font-display text-[15px] font-bold tracking-tight [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [[data-desktop]_&]:h-11\",\n nav: \"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2\",\n group: \"mt-3 mb-1 px-3 font-mono text-[10.5px] font-semibold text-fg-secondary [[data-collapsed]_&]:sr-only\",\n item:\n \"group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n \"aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg \" +\n \"aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-full aria-[current=page]:before:bg-accent \" +\n \"[[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0 \" +\n \"[[data-desktop]_&]:h-8 [[data-desktop]_&]:cursor-default [[data-desktop]_&]:rounded-md [[data-desktop]_&]:text-[13px] [[data-desktop]_&_svg]:h-4 [[data-desktop]_&_svg]:w-4\",\n itemLabel: \"min-w-0 flex-1 truncate [[data-collapsed]_&]:sr-only\",\n itemHint: \"ml-auto hidden items-center gap-0.5 text-fg-secondary group-hover/item:flex group-focus-visible/item:flex [[data-collapsed]_&]:!hidden\",\n itemBadge:\n \"ml-auto inline-flex min-w-5 items-center justify-center rounded-full bg-accent px-1.5 text-[11px] font-bold text-on-accent \" +\n \"[[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]\",\n footer: \"shrink-0 border-t border-border-subtle p-2\",\n collapse:\n \"hidden h-9 w-full items-center gap-2 rounded-lg px-3 text-xs font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg md:flex \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0\",\n body: \"flex min-w-0 flex-1 flex-col [[data-desktop]_&]:min-h-0 [[data-desktop]_&]:overflow-y-auto\",\n header:\n \"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border-subtle bg-surface-0 px-4 md:px-6 \" +\n \"[[data-desktop]_&]:h-12 [[data-desktop]_&]:select-none [[data-desktop]_&]:px-5\",\n menuButton:\n \"inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border-subtle bg-surface-1 text-fg md:hidden [[data-desktop]_&]:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n title: \"min-w-0 flex-1 truncate font-display text-base font-bold tracking-tight [[data-desktop]_&]:text-[15px]\",\n search:\n \"inline-flex h-9 min-w-9 items-center justify-center gap-2 rounded-lg border border-border-subtle bg-surface-1 px-2.5 text-sm text-fg-secondary transition-colors \" +\n \"hover:border-border-default hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring md:w-56 md:justify-start [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0\",\n searchLabel: \"sr-only md:not-sr-only md:flex-1 md:truncate md:text-left\",\n searchKeys: \"hidden items-center gap-0.5 md:flex\",\n actions: \"ml-auto flex shrink-0 items-center gap-2\",\n main: \"min-w-0 flex-1 px-4 py-6 md:px-6 md:py-8 [[data-desktop]_&]:px-6 [[data-desktop]_&]:py-6\",\n },\n});\nconst s = appShell();\n\ninterface ShellState {\n collapsed: boolean;\n setCollapsed: (value: boolean) => void;\n open: boolean;\n setOpen: (value: boolean) => void;\n inDrawer: boolean;\n desktop: boolean;\n}\nconst ShellContext = React.createContext<ShellState | null>(null);\nconst useShell = () => {\n const ctx = React.useContext(ShellContext);\n if (!ctx) throw new Error(\"AppShell parts must be inside <AppShell.Root>\");\n return ctx;\n};\n\nexport type AppShellRootProps = React.ComponentProps<\"div\"> & {\n /** The sidebar starts collapsed to its icons (desktop). */\n defaultCollapsed?: boolean;\n /** Controlled collapse (desktop): pair with `onCollapsedChange` to persist the choice. */\n collapsed?: boolean;\n /** Called when the sidebar collapses or expands. */\n onCollapsedChange?: (collapsed: boolean) => void;\n /** The words of the \"skip to content\" link (`Skip to content`), the first tab stop; it lands on `AppShell.Main`. Without it, no link. */\n skipLabel?: string;\n /**\n * The shortcut that folds and unfolds the sidebar (a combination: `mod+b` is ⌘B on a Mac, Ctrl+B elsewhere), or `false`\n * for none.\n * @default \"mod+b\"\n */\n collapseShortcut?: string | false;\n /**\n * The shell is a desktop app's window (the Orazaka Mac app, Tauri 2 — ADR-002): no drawer, the sidebar's top clears the\n * window's traffic lights (`--kz-titlebar-inset`, 28 px; the window's height is `--kz-window-height`, the viewport by default), the header and that top move the window\n * (`data-tauri-drag-region`), a denser layout, the chrome not selectable. In a browser it is a fixed-height shell.\n */\n desktop?: boolean;\n};\n\n/** The shell: holds the collapse (desktop) and the drawer (phone) state. Children: Sidebar, then Body. */\nexport function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, skipLabel, collapseShortcut = \"mod+b\", desktop = false, className, children, ...props }: AppShellRootProps) {\n const [own, setOwn] = React.useState(defaultCollapsed);\n const [open, setOpen] = React.useState(false);\n const collapsed = controlled ?? own;\n const setCollapsed = React.useCallback(\n (value: boolean) => {\n if (controlled === undefined) setOwn(value);\n onCollapsedChange?.(value);\n },\n [controlled, onCollapsedChange],\n );\n const value = React.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false, desktop }), [collapsed, setCollapsed, open, desktop]);\n useShortcuts(collapseShortcut ? { [collapseShortcut]: () => setCollapsed(!collapsed) } : {});\n return (\n <ShellContext.Provider value={value}>\n <div data-desktop={desktop ? \"\" : undefined} className={s.root({ className })} {...props}>\n {skipLabel && <SkipLink href=\"#content\">{skipLabel}</SkipLink>}\n {children}\n </div>\n </ShellContext.Provider>\n );\n}\n\nexport type AppShellSidebarProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n /** The navigation's accessible name (`Studio`, `Administration`). */\n label: string;\n /** The brand at the top: the product's mark and name (a link home through `asChild` of your router, or plain). */\n brand?: React.ReactNode;\n /** The sections: `AppShell.Group` and `AppShell.Item`. */\n children: React.ReactNode;\n /** At the bottom, above the collapse button: the account, a plan, the version. */\n footer?: React.ReactNode;\n /** The collapse button's words, `[collapse, expand]` (desktop only). Without it the sidebar cannot collapse. */\n collapseLabels?: readonly [string, string];\n /** The drawer's close button words (phone). */\n closeLabel: string;\n};\n\nfunction SidebarContent({ brand, children, footer, collapseLabels, closeLabel }: Pick<AppShellSidebarProps, \"brand\" | \"children\" | \"footer\" | \"collapseLabels\" | \"closeLabel\">) {\n const { collapsed, setCollapsed, inDrawer, desktop } = useShell();\n const drag = desktop ? { \"data-tauri-drag-region\": \"\" } : {};\n return (\n <>\n {desktop && <div aria-hidden className={s.titlebar()} {...drag} />}\n <div className={s.brand()} {...drag}>\n {brand}\n {inDrawer && (\n <DialogPrimitive.Close className={cn(s.menuButton(), \"ml-auto flex\")} aria-label={closeLabel}>\n <CloseGlyph className=\"h-4 w-4\" />\n </DialogPrimitive.Close>\n )}\n </div>\n <div className={s.nav()}>{children}</div>\n {(footer || (collapseLabels && !inDrawer)) && (\n <div className={s.footer()}>\n {footer}\n {collapseLabels && !inDrawer && (\n <button type=\"button\" className={s.collapse()} aria-expanded={!collapsed} onClick={() => setCollapsed(!collapsed)}>\n <ChevronGlyph direction={collapsed ? \"right\" : \"left\"} className=\"h-4 w-4\" />\n <span className={collapsed ? \"sr-only\" : undefined}>{collapsed ? collapseLabels[1] : collapseLabels[0]}</span>\n </button>\n )}\n </div>\n )}\n </>\n );\n}\n\n/** The sidebar: on a desktop a column that collapses to its icons; on a phone the same content in a drawer. */\nexport function AppShellSidebar({ label, brand, children, footer, collapseLabels, closeLabel, className, ...props }: AppShellSidebarProps) {\n const shell = useShell();\n const content = { brand, children, footer, collapseLabels, closeLabel };\n return (\n <>\n <nav aria-label={label} data-collapsed={shell.collapsed ? \"\" : undefined} className={s.sidebar({ className })} {...props}>\n <SidebarContent {...content} />\n </nav>\n {!shell.desktop && (\n <DialogPrimitive.Root open={shell.open} onOpenChange={shell.setOpen}>\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay className={s.overlay()} />\n <DialogPrimitive.Content className={s.drawer()} aria-describedby={undefined}>\n <DialogPrimitive.Title className=\"sr-only\">{label}</DialogPrimitive.Title>\n <ShellContext.Provider value={{ ...shell, collapsed: false, inDrawer: true }}>\n <nav aria-label={label} className=\"flex min-h-0 flex-1 flex-col\" onClick={(e) => (e.target as Element).closest(\"a\") && shell.setOpen(false)}>\n <SidebarContent {...content} />\n </nav>\n </ShellContext.Provider>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n )}\n </>\n );\n}\n\nexport type AppShellGroupProps = React.ComponentProps<\"p\"> & {\n /** The group's name, above its items (hidden when the sidebar is collapsed). */\n children: React.ReactNode;\n};\n\n/** A heading between groups of items. */\nexport function AppShellGroup({ className, ...props }: AppShellGroupProps) {\n return <p className={s.group({ className })} {...props} />;\n}\n\nexport type AppShellItemProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The item's icon (a signature icon): it stays when the sidebar collapses. */\n icon?: React.ReactNode;\n /** Its words: the accessible name, hidden visually when collapsed (the icon carries a tooltip through `title`). */\n label: string;\n /** The screen it opens is the current one: `aria-current=\"page\"`. */\n current?: boolean;\n /** A count beside it (unread, pending): a number or a few characters. */\n badge?: React.ReactNode;\n /**\n * A keyboard shortcut that goes to it: a sequence (`g d`: G then D) or a combination (`mod+1`). It clicks the item, so a\n * router link navigates. The keys are shown on hover and focus when the sidebar is open.\n */\n shortcut?: string;\n /** Renders your link (Next's `Link`) with the item's look: pass it as the only child. */\n asChild?: boolean;\n /** Your link element when `asChild`. */\n children?: React.ReactElement;\n};\n\n/** A destination of the sidebar: icon, words, a count. */\nexport function AppShellItem({ icon, label, current, badge, shortcut, asChild, children, className, ...props }: AppShellItemProps) {\n const { collapsed, inDrawer } = useShell();\n const element = React.useRef<HTMLAnchorElement>(null);\n const apple = useApple();\n // Only the sidebar's own items listen: the drawer holds a copy of them, open or not.\n useShortcuts(shortcut && !inDrawer ? { [shortcut]: () => element.current?.click() } : {});\n const Comp = asChild ? Slot.Root : \"a\";\n const inner = (\n <>\n {icon}\n <span className={s.itemLabel()}>{label}</span>\n {badge != null && <span className={s.itemBadge()}>{badge}</span>}\n {shortcut && !inDrawer ? (\n <span className={s.itemHint()} aria-hidden>\n {formatShortcut(shortcut, apple).map((key) => (\n <Kbd key={key} size=\"sm\">\n {key}\n </Kbd>\n ))}\n </span>\n ) : null}\n </>\n );\n return (\n <Comp ref={element} aria-current={current ? \"page\" : undefined} aria-keyshortcuts={shortcut ? ariaKeyShortcuts(shortcut, apple) : undefined} title={collapsed ? label : undefined} className={s.item({ className })} {...props}>\n {asChild && children ? React.cloneElement(children, undefined, inner) : inner}\n </Comp>\n );\n}\n\n/** The column right of the sidebar: Header, then Main. */\nexport function AppShellBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport type AppShellHeaderProps = Omit<React.ComponentProps<\"header\">, \"title\"> & {\n /** The screen's title (an `h1`). */\n title?: React.ReactNode;\n /** The button that opens the drawer on a phone: its words. */\n menuLabel: string;\n /** The screen's actions, on the right. */\n actions?: React.ReactNode;\n};\n\n/** The top of the screen: the drawer's button (phone), the title, the actions. Sticky. */\nexport function AppShellHeader({ title, menuLabel, actions, className, children, ...props }: AppShellHeaderProps) {\n const { open, setOpen, desktop } = useShell();\n const drag = desktop ? { \"data-tauri-drag-region\": \"\" } : {};\n return (\n <header className={s.header({ className })} {...drag} {...props}>\n <button type=\"button\" className={s.menuButton()} aria-label={menuLabel} aria-expanded={open} aria-haspopup=\"dialog\" onClick={() => setOpen(true)}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M4 7h16M4 12h16M4 17h10\" />\n </svg>\n </button>\n {title && (\n <h1 className={s.title()} {...drag}>\n {title}\n </h1>\n )}\n {children}\n {actions && <div className={s.actions()}>{actions}</div>}\n </header>\n );\n}\n\nexport type AppShellSearchProps = Omit<React.ComponentProps<\"button\">, \"children\" | \"onClick\"> & {\n /** The words (\"Search\", \"Search creators\"): the accessible name, shown beside the keys on a desktop. */\n label: string;\n /** Called when the button is pressed or the shortcut is typed: open your command palette (`CommandDialog`) here. */\n onOpen: () => void;\n /**\n * The shortcut that opens it (a combination); shown on its keys.\n * @default \"mod+k\"\n */\n shortcut?: string;\n};\n\n/** The header's search: a button that looks like a field (a magnifier, its words, ⌘K) and opens the app's command palette. */\nexport function AppShellSearch({ label, onOpen, shortcut = \"mod+k\", className, ...props }: AppShellSearchProps) {\n const apple = useApple();\n useShortcuts({ [shortcut]: () => onOpen() });\n return (\n <button type=\"button\" className={s.search({ className })} aria-keyshortcuts={ariaKeyShortcuts(shortcut, apple)} onClick={onOpen} {...props}>\n <SearchGlyph />\n <span className={s.searchLabel()}>{label}</span>\n <span className={s.searchKeys()} aria-hidden>\n {formatShortcut(shortcut, apple).map((key) => (\n <Kbd key={key} size=\"sm\">\n {key}\n </Kbd>\n ))}\n </span>\n </button>\n );\n}\n\n/** The screen's content. */\nexport function AppShellMain({ className, ...props }: React.ComponentProps<\"main\">) {\n return <main id=\"content\" tabIndex={-1} className={s.main({ className })} {...props} />;\n}\n\nexport const AppShell = {\n Root: AppShellRoot,\n Sidebar: AppShellSidebar,\n Group: AppShellGroup,\n Item: AppShellItem,\n Body: AppShellBody,\n Header: AppShellHeader,\n Search: AppShellSearch,\n Main: AppShellMain,\n};\n","// Server-safe. A key, or a shortcut of several (`<Kbd>⌘</Kbd><Kbd>K</Kbd>`).\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const kbdVariants = tv({\n base:\n \"inline-flex shrink-0 items-center justify-center rounded-md border border-border-default border-b-2 bg-surface-2 \" +\n \"font-mono font-semibold text-fg-secondary\",\n variants: { size: { sm: \"h-5 min-w-5 px-1 text-[10px]\", md: \"h-6 min-w-6 px-1.5 text-xs\" } },\n defaultVariants: { size: \"md\" },\n});\n\nexport type KbdProps = React.ComponentProps<\"kbd\"> & {\n /**\n * sm · md.\n * @default \"md\"\n */\n size?: VariantProps<typeof kbdVariants>[\"size\"];\n};\n\nexport function Kbd({ size, className, ...props }: KbdProps) {\n return <kbd className={kbdVariants({ size, className })} {...props} />;\n}\n","// Keyboard shortcuts for an application: a combination (`mod+k`, `shift+/`, `alt+arrowup`) or a sequence (`g d`: press G,\n// then D). `mod` is ⌘ on Apple devices and Ctrl elsewhere. Plain keys and sequences stay out of text fields; a combination\n// with `mod` works in an input (not in a rich-text editor, where ⌘B is bold). Nothing is decided here about the words.\nimport * as React from \"react\";\n\nimport { useLatest } from \"../media\";\n\nexport type ShortcutMap = Readonly<Record<string, ((event: KeyboardEvent) => void) | undefined>>;\n\nconst isApple = () => typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n\nconst ALIASES: Record<string, string> = { esc: \"escape\", return: \"enter\", space: \" \", spacebar: \" \", up: \"arrowup\", down: \"arrowdown\", left: \"arrowleft\", right: \"arrowright\", del: \"delete\", plus: \"+\" };\ntype Modifier = \"mod\" | \"ctrl\" | \"alt\" | \"shift\" | \"meta\";\n\ntype Combo = { key: string; mod: boolean; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean };\n\n/** `\"mod+shift+k\"` → its parts. A lone `+` key is written `plus`. */\nexport function parseCombo(combo: string): Combo {\n const parts = combo.toLowerCase().split(\"+\").filter(Boolean);\n const key = ALIASES[parts.at(-1) ?? \"\"] ?? parts.at(-1) ?? \"\";\n const has = (name: Modifier) => parts.slice(0, -1).includes(name);\n return { key, mod: has(\"mod\"), ctrl: has(\"ctrl\"), alt: has(\"alt\"), shift: has(\"shift\"), meta: has(\"meta\") };\n}\n\n/** Whether a key event is this combination: its modifiers exactly (`mod` is ⌘ or Ctrl by platform), then its key. */\nexport function matchesCombo(event: Pick<KeyboardEvent, \"key\" | \"metaKey\" | \"ctrlKey\" | \"altKey\" | \"shiftKey\">, combo: string, apple = isApple()): boolean {\n const want = parseCombo(combo);\n const meta = want.meta || (want.mod && apple);\n const ctrl = want.ctrl || (want.mod && !apple);\n if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== want.alt) return false;\n // Shift is only checked when the binding names it: `?` and `/` need it on most layouts.\n if (want.shift && !event.shiftKey) return false;\n return event.key.toLowerCase() === want.key;\n}\n\n/** The keys of a shortcut to show on a keycap: `[\"⌘\", \"K\"]` on a Mac, `[\"Ctrl\", \"K\"]` elsewhere; a sequence `g d` is `[\"G\", \"D\"]`. */\nexport function formatShortcut(shortcut: string, apple = isApple()): string[] {\n if (shortcut.includes(\" \")) return shortcut.split(/\\s+/).map((key) => key.toUpperCase());\n const parts = shortcut.split(\"+\").filter(Boolean);\n const key = parts.at(-1) ?? \"\";\n const names: Record<string, [string, string]> = { mod: [\"⌘\", \"Ctrl\"], meta: [\"⌘\", \"Win\"], ctrl: [\"⌃\", \"Ctrl\"], alt: [\"⌥\", \"Alt\"], shift: [\"⇧\", \"Shift\"] };\n const shown = parts.slice(0, -1).map((part) => names[part.toLowerCase()]?.[apple ? 0 : 1] ?? part);\n const glyph: Record<string, string> = { escape: \"Esc\", enter: \"↵\", arrowup: \"↑\", arrowdown: \"↓\", arrowleft: \"←\", arrowright: \"→\", \" \": \"Space\" };\n const last = glyph[ALIASES[key.toLowerCase()] ?? key.toLowerCase()] ?? key.toUpperCase();\n return [...shown, last];\n}\n\n/**\n * The accelerator of a combination for a desktop app's native menu bar (Tauri's `MenuItem`, Electron's — ADR-002):\n * `mod+k` → `CmdOrCtrl+K`, `shift+mod+/` → `Shift+CmdOrCtrl+/`. The menu and the shell then answer the same keys, and the\n * menu shows them. A sequence (`g d`) has none: a menu item takes one chord.\n */\nexport function acceleratorOf(shortcut: string): string | undefined {\n if (shortcut.trim().includes(\" \")) return undefined;\n const parts = shortcut.split(\"+\").filter(Boolean);\n const names: Record<string, string> = { mod: \"CmdOrCtrl\", meta: \"Super\", ctrl: \"Ctrl\", alt: \"Alt\", shift: \"Shift\" };\n const raw = (parts.at(-1) ?? \"\").toLowerCase();\n const key = ALIASES[raw] ?? raw;\n const keys: Record<string, string> = { escape: \"Escape\", enter: \"Enter\", \" \": \"Space\", arrowup: \"Up\", arrowdown: \"Down\", arrowleft: \"Left\", arrowright: \"Right\", delete: \"Delete\", backspace: \"Backspace\", tab: \"Tab\", \"+\": \"Plus\" };\n const last = keys[key] ?? (key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1));\n return [...parts.slice(0, -1).map((part) => names[part.toLowerCase()] ?? part), last].join(\"+\");\n}\n\nconst noop = () => () => undefined;\n\n/** Whether the person is on an Apple device — `false` on the server and in the first client render (no mismatch), then right. */\nexport function useApple(): boolean {\n return React.useSyncExternalStore(noop, isApple, () => false);\n}\n\n/** The `aria-keyshortcuts` of a combination (`Control+K`, `Meta+K` on a Mac); a sequence has none (it is not a chord). */\nexport function ariaKeyShortcuts(shortcut: string, apple = isApple()): string | undefined {\n if (shortcut.includes(\" \")) return undefined;\n const parts = shortcut.split(\"+\").filter(Boolean);\n const names: Record<string, string> = { mod: apple ? \"Meta\" : \"Control\", meta: \"Meta\", ctrl: \"Control\", alt: \"Alt\", shift: \"Shift\" };\n const key = parts.at(-1) ?? \"\";\n return [...parts.slice(0, -1).map((part) => names[part.toLowerCase()] ?? part), key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)].join(\"+\");\n}\n\n/** Whether a key press happens in something you type in. */\nfunction isEditable(target: EventTarget | null): { field: boolean; rich: boolean } {\n const element = target instanceof HTMLElement ? target : null;\n if (!element) return { field: false, rich: false };\n const rich = element.isContentEditable;\n return { field: rich || /^(input|textarea|select)$/i.test(element.tagName), rich };\n}\n\n/**\n * Binds shortcuts for as long as the component lives (`enabled` turns them off). Each key is a combination or a sequence,\n * each value runs when it is pressed; the handler may change on every render. A shortcut that fires is `preventDefault`ed.\n * A sequence waits `timeout` ms for its next key.\n */\nexport function useShortcuts(map: ShortcutMap, { enabled = true, timeout = 900 }: { enabled?: boolean; timeout?: number } = {}) {\n const latest = useLatest(map);\n const names = Object.keys(map).sort().join(\"\\n\");\n React.useEffect(() => {\n if (!enabled) return;\n const bindings = names.split(\"\\n\").filter(Boolean);\n let buffer: { key: string; at: number }[] = [];\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.defaultPrevented || event.isComposing || event.repeat || !event.key) return;\n const editing = isEditable(event.target);\n const chord = event.metaKey || event.ctrlKey || event.altKey;\n const now = Date.now();\n buffer = [...buffer.filter((entry) => now - entry.at <= timeout), { key: event.key.toLowerCase(), at: now }].slice(-4);\n for (const binding of bindings) {\n const handler = latest.current[binding];\n if (!handler) continue;\n const keys = binding.toLowerCase().split(/\\s+/).map((key) => ALIASES[key] ?? key);\n const fired =\n keys.length > 1\n ? !chord && !editing.field && buffer.length >= keys.length && keys.every((key, i) => buffer[buffer.length - keys.length + i]?.key === key)\n : matchesCombo(event, binding) && (chord ? !editing.rich : !editing.field);\n if (!fired) continue;\n event.preventDefault();\n buffer = [];\n handler(event);\n return;\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [enabled, names, latest, timeout]);\n}\n","// Shared by the data and media primitives (client): the clock format, the reduced-motion preference, the\n// in-view observer, a debounced callback. Never an entry of its own.\nimport * as React from \"react\";\n\n/** Seconds as a clock: `75` → `1:15`, `3725` → `1:02:05`. Negative or unknown values read `0:00`. */\nexport function formatClock(seconds: number): string {\n const s = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const rest = String(s % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${rest}` : `${m}:${rest}`;\n}\n\n/** Clamps `value` between `min` and `max`. */\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst REDUCE = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduce = (notify: () => void) => {\n if (typeof window === \"undefined\" || !window.matchMedia) return () => undefined;\n const query = window.matchMedia(REDUCE);\n query.addEventListener?.(\"change\", notify);\n return () => query.removeEventListener?.(\"change\", notify);\n};\n\n/** `true` when the person asked for less motion (`prefers-reduced-motion: reduce`); follows a change of the setting. */\nexport function usePrefersReducedMotion(): boolean {\n return React.useSyncExternalStore(\n subscribeReduce,\n () => (typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(REDUCE).matches : false),\n () => false,\n );\n}\n\n/** The latest value of a callback, for an effect or an event that must not re-subscribe when it changes. */\nexport function useLatest<T>(value: T) {\n const ref = React.useRef(value);\n React.useLayoutEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n\n/** Calls `onChange(visible)` when at least `threshold` of the element enters or leaves the viewport. */\nexport function useInView(ref: React.RefObject<Element | null>, onChange: (visible: boolean) => void, enabled = true, threshold = 0.25) {\n const latest = useLatest(onChange);\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(([entry]) => entry && latest.current(entry.isIntersecting), { threshold });\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled, threshold, latest]);\n}\n\n/** A value controlled by a prop when it is given, held inside otherwise; `set` calls `onChange` either way. */\nexport function useControllable<T>(value: T | undefined, defaultValue: T, onChange?: (next: T) => void) {\n const [inner, setInner] = React.useState(defaultValue);\n const controlled = value !== undefined;\n const current = controlled ? value : inner;\n const set = React.useCallback(\n (next: T) => {\n if (!controlled) setInner(next);\n onChange?.(next);\n },\n [controlled, onChange],\n );\n return [current, set] as const;\n}\n\n/** Starts a media element without an unhandled rejection (autoplay refused, the source replaced meanwhile). */\nexport function playSafely(media: HTMLMediaElement | null | undefined) {\n try {\n const started = media?.play() as Promise<void> | undefined;\n started?.catch?.(() => undefined);\n } catch {\n // Not playable here (no media support): the controls stay as they are.\n }\n}\n"],"mappings":"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","names":["React","tv","React","key","jsx","tv","value"]}
1
+ {"version":3,"sources":["../src/app-shell/app-shell.tsx","../src/kbd/kbd.tsx","../src/app-shell/shortcuts.ts","../src/media.ts"],"sourcesContent":["// Client (the collapse, the drawer, the shortcuts). The shell of an application screen, read from the products' consoles\n// (Orochia admin and studio, Orazaka web-admin and web-client): a sidebar of sections that collapses to its icons on a\n// desktop, a header with the screen's title, a search that opens the app's command palette and its actions, the content —\n// and below `md` the sidebar becomes a drawer opened from the header (Radix Dialog: focus trap, Escape, focus return).\n// Keyboard: ⌘/Ctrl+B folds the sidebar, ⌘/Ctrl+K opens the search, `g` then a letter goes to a section (`shortcut` of an\n// item). Words arrive as props; links through `asChild`.\n// `desktop` (ADR-002): the shell is the window of a desktop app (the Orazaka Mac app, Tauri 2): the sidebar is always\n// there (a window has a minimum width, no drawer), its top clears the traffic lights, the header and the sidebar's top are\n// the title bar (`data-tauri-drag-region`: dragging them moves the window), the density is the desktop's (32 px rows,\n// 13 px text, a 48 px header), the chrome is not selectable, and the content scrolls under a fixed header.\nimport { SkipLink } from \"@krizaka/ui/skip-link\";\nimport { Dialog as DialogPrimitive, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { ChevronGlyph, CloseGlyph, SearchGlyph } from \"../glyphs\";\nimport { Kbd } from \"../kbd\";\nimport { ariaKeyShortcuts, formatShortcut, useApple, useShortcuts } from \"./shortcuts\";\n\nexport const appShell = tv({\n slots: {\n root: \"flex min-h-dvh w-full bg-surface-0 text-fg data-desktop:h-[var(--kz-window-height,100dvh)] data-desktop:overflow-hidden\",\n sidebar:\n \"sticky top-0 hidden h-dvh shrink-0 flex-col border-r border-border-subtle bg-surface-1 transition-[width] duration-300 motion-reduce:transition-none md:flex \" +\n \"w-64 data-collapsed:w-[4.25rem] \" +\n \"[[data-desktop]_&]:static [[data-desktop]_&]:flex [[data-desktop]_&]:h-auto [[data-desktop]_&]:w-60 [[data-desktop]_&]:select-none [[data-desktop]_&]:data-collapsed:w-[4.75rem]\",\n titlebar: \"h-[var(--kz-titlebar-inset,1.75rem)] shrink-0\",\n drawer:\n \"kz-dialog fixed inset-y-0 left-0 z-50 flex w-[min(20rem,88vw)] flex-col border-r border-border-default bg-surface-1 pl-[env(safe-area-inset-left)] shadow-lg focus:outline-hidden\",\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay\",\n brand: \"flex h-14 shrink-0 items-center gap-2.5 px-4 font-display text-[15px] font-bold tracking-tight [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [[data-desktop]_&]:h-11\",\n nav: \"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2\",\n group: \"mt-3 mb-1 px-3 font-mono text-[10.5px] font-semibold text-fg-secondary [[data-collapsed]_&]:sr-only\",\n item:\n \"group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n \"aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg \" +\n \"aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-sm aria-[current=page]:before:bg-accent \" +\n \"[[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0 \" +\n \"[[data-desktop]_&]:h-8 [[data-desktop]_&]:cursor-default [[data-desktop]_&]:rounded-md [[data-desktop]_&]:text-[13px] [[data-desktop]_&_svg]:h-4 [[data-desktop]_&_svg]:w-4\",\n itemLabel: \"min-w-0 flex-1 truncate [[data-collapsed]_&]:sr-only\",\n itemHint: \"ml-auto hidden items-center gap-0.5 text-fg-secondary group-hover/item:flex group-focus-visible/item:flex [[data-collapsed]_&]:!hidden\",\n itemBadge:\n \"ml-auto inline-flex min-w-5 items-center justify-center rounded-sm bg-accent px-1.5 text-[11px] font-bold text-on-accent \" +\n \"[[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]\",\n footer: \"shrink-0 border-t border-border-subtle p-2\",\n collapse:\n \"hidden h-9 w-full items-center gap-2 rounded-lg px-3 text-xs font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg md:flex \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0\",\n body: \"flex min-w-0 flex-1 flex-col [[data-desktop]_&]:min-h-0 [[data-desktop]_&]:overflow-y-auto\",\n header:\n \"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border-subtle bg-surface-0 px-4 md:px-6 \" +\n \"[[data-desktop]_&]:h-12 [[data-desktop]_&]:select-none [[data-desktop]_&]:px-5\",\n menuButton:\n \"inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border-subtle bg-surface-1 text-fg md:hidden [[data-desktop]_&]:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n title: \"min-w-0 flex-1 truncate font-display text-base font-bold tracking-tight [[data-desktop]_&]:text-[15px]\",\n search:\n \"inline-flex h-9 min-w-9 items-center justify-center gap-2 rounded-lg border border-border-subtle bg-surface-1 px-2.5 text-sm text-fg-secondary transition-colors \" +\n \"hover:border-border-default hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring md:w-56 md:justify-start [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0\",\n searchLabel: \"sr-only md:not-sr-only md:flex-1 md:truncate md:text-left\",\n searchKeys: \"hidden items-center gap-0.5 md:flex\",\n actions: \"ml-auto flex shrink-0 items-center gap-2\",\n main: \"min-w-0 flex-1 px-4 py-6 md:px-6 md:py-8 [[data-desktop]_&]:px-6 [[data-desktop]_&]:py-6\",\n },\n});\nconst s = appShell();\n\ninterface ShellState {\n collapsed: boolean;\n setCollapsed: (value: boolean) => void;\n open: boolean;\n setOpen: (value: boolean) => void;\n inDrawer: boolean;\n desktop: boolean;\n}\nconst ShellContext = React.createContext<ShellState | null>(null);\nconst useShell = () => {\n const ctx = React.useContext(ShellContext);\n if (!ctx) throw new Error(\"AppShell parts must be inside <AppShell.Root>\");\n return ctx;\n};\n\nexport type AppShellRootProps = React.ComponentProps<\"div\"> & {\n /** The sidebar starts collapsed to its icons (desktop). */\n defaultCollapsed?: boolean;\n /** Controlled collapse (desktop): pair with `onCollapsedChange` to persist the choice. */\n collapsed?: boolean;\n /** Called when the sidebar collapses or expands. */\n onCollapsedChange?: (collapsed: boolean) => void;\n /** The words of the \"skip to content\" link (`Skip to content`), the first tab stop; it lands on `AppShell.Main`. Without it, no link. */\n skipLabel?: string;\n /**\n * The shortcut that folds and unfolds the sidebar (a combination: `mod+b` is ⌘B on a Mac, Ctrl+B elsewhere), or `false`\n * for none.\n * @default \"mod+b\"\n */\n collapseShortcut?: string | false;\n /**\n * The shell is a desktop app's window (the Orazaka Mac app, Tauri 2 — ADR-002): no drawer, the sidebar's top clears the\n * window's traffic lights (`--kz-titlebar-inset`, 28 px; the window's height is `--kz-window-height`, the viewport by default), the header and that top move the window\n * (`data-tauri-drag-region`), a denser layout, the chrome not selectable. In a browser it is a fixed-height shell.\n */\n desktop?: boolean;\n};\n\n/** The shell: holds the collapse (desktop) and the drawer (phone) state. Children: Sidebar, then Body. */\nexport function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, skipLabel, collapseShortcut = \"mod+b\", desktop = false, className, children, ...props }: AppShellRootProps) {\n const [own, setOwn] = React.useState(defaultCollapsed);\n const [open, setOpen] = React.useState(false);\n const collapsed = controlled ?? own;\n const setCollapsed = React.useCallback(\n (value: boolean) => {\n if (controlled === undefined) setOwn(value);\n onCollapsedChange?.(value);\n },\n [controlled, onCollapsedChange],\n );\n const value = React.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false, desktop }), [collapsed, setCollapsed, open, desktop]);\n useShortcuts(collapseShortcut ? { [collapseShortcut]: () => setCollapsed(!collapsed) } : {});\n return (\n <ShellContext.Provider value={value}>\n <div data-desktop={desktop ? \"\" : undefined} className={s.root({ className })} {...props}>\n {skipLabel && <SkipLink href=\"#content\">{skipLabel}</SkipLink>}\n {children}\n </div>\n </ShellContext.Provider>\n );\n}\n\nexport type AppShellSidebarProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n /** The navigation's accessible name (`Studio`, `Administration`). */\n label: string;\n /** The brand at the top: the product's mark and name (a link home through `asChild` of your router, or plain). */\n brand?: React.ReactNode;\n /** The sections: `AppShell.Group` and `AppShell.Item`. */\n children: React.ReactNode;\n /** At the bottom, above the collapse button: the account, a plan, the version. */\n footer?: React.ReactNode;\n /** The collapse button's words, `[collapse, expand]` (desktop only). Without it the sidebar cannot collapse. */\n collapseLabels?: readonly [string, string];\n /** The drawer's close button words (phone). */\n closeLabel: string;\n};\n\nfunction SidebarContent({ brand, children, footer, collapseLabels, closeLabel }: Pick<AppShellSidebarProps, \"brand\" | \"children\" | \"footer\" | \"collapseLabels\" | \"closeLabel\">) {\n const { collapsed, setCollapsed, inDrawer, desktop } = useShell();\n const drag = desktop ? { \"data-tauri-drag-region\": \"\" } : {};\n return (\n <>\n {desktop && <div aria-hidden className={s.titlebar()} {...drag} />}\n <div className={s.brand()} {...drag}>\n {brand}\n {inDrawer && (\n <DialogPrimitive.Close className={cn(s.menuButton(), \"ml-auto flex\")} aria-label={closeLabel}>\n <CloseGlyph className=\"h-4 w-4\" />\n </DialogPrimitive.Close>\n )}\n </div>\n <div className={s.nav()}>{children}</div>\n {(footer || (collapseLabels && !inDrawer)) && (\n <div className={s.footer()}>\n {footer}\n {collapseLabels && !inDrawer && (\n <button type=\"button\" className={s.collapse()} aria-expanded={!collapsed} onClick={() => setCollapsed(!collapsed)}>\n <ChevronGlyph direction={collapsed ? \"right\" : \"left\"} className=\"h-4 w-4\" />\n <span className={collapsed ? \"sr-only\" : undefined}>{collapsed ? collapseLabels[1] : collapseLabels[0]}</span>\n </button>\n )}\n </div>\n )}\n </>\n );\n}\n\n/** The sidebar: on a desktop a column that collapses to its icons; on a phone the same content in a drawer. */\nexport function AppShellSidebar({ label, brand, children, footer, collapseLabels, closeLabel, className, ...props }: AppShellSidebarProps) {\n const shell = useShell();\n const content = { brand, children, footer, collapseLabels, closeLabel };\n return (\n <>\n <nav aria-label={label} data-collapsed={shell.collapsed ? \"\" : undefined} className={s.sidebar({ className })} {...props}>\n <SidebarContent {...content} />\n </nav>\n {!shell.desktop && (\n <DialogPrimitive.Root open={shell.open} onOpenChange={shell.setOpen}>\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay className={s.overlay()} />\n <DialogPrimitive.Content className={s.drawer()} aria-describedby={undefined}>\n <DialogPrimitive.Title className=\"sr-only\">{label}</DialogPrimitive.Title>\n <ShellContext.Provider value={{ ...shell, collapsed: false, inDrawer: true }}>\n <nav aria-label={label} className=\"flex min-h-0 flex-1 flex-col\" onClick={(e) => (e.target as Element).closest(\"a\") && shell.setOpen(false)}>\n <SidebarContent {...content} />\n </nav>\n </ShellContext.Provider>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n )}\n </>\n );\n}\n\nexport type AppShellGroupProps = React.ComponentProps<\"p\"> & {\n /** The group's name, above its items (hidden when the sidebar is collapsed). */\n children: React.ReactNode;\n};\n\n/** A heading between groups of items. */\nexport function AppShellGroup({ className, ...props }: AppShellGroupProps) {\n return <p className={s.group({ className })} {...props} />;\n}\n\nexport type AppShellItemProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The item's icon (a signature icon): it stays when the sidebar collapses. */\n icon?: React.ReactNode;\n /** Its words: the accessible name, hidden visually when collapsed (the icon carries a tooltip through `title`). */\n label: string;\n /** The screen it opens is the current one: `aria-current=\"page\"`. */\n current?: boolean;\n /** A count beside it (unread, pending): a number or a few characters. */\n badge?: React.ReactNode;\n /**\n * A keyboard shortcut that goes to it: a sequence (`g d`: G then D) or a combination (`mod+1`). It clicks the item, so a\n * router link navigates. The keys are shown on hover and focus when the sidebar is open.\n */\n shortcut?: string;\n /** Renders your link (Next's `Link`) with the item's look: pass it as the only child. */\n asChild?: boolean;\n /** Your link element when `asChild`. */\n children?: React.ReactElement;\n};\n\n/** A destination of the sidebar: icon, words, a count. */\nexport function AppShellItem({ icon, label, current, badge, shortcut, asChild, children, className, ...props }: AppShellItemProps) {\n const { collapsed, inDrawer } = useShell();\n const element = React.useRef<HTMLAnchorElement>(null);\n const apple = useApple();\n // Only the sidebar's own items listen: the drawer holds a copy of them, open or not.\n useShortcuts(shortcut && !inDrawer ? { [shortcut]: () => element.current?.click() } : {});\n const Comp = asChild ? Slot.Root : \"a\";\n const inner = (\n <>\n {icon}\n <span className={s.itemLabel()}>{label}</span>\n {badge != null && <span className={s.itemBadge()}>{badge}</span>}\n {shortcut && !inDrawer ? (\n <span className={s.itemHint()} aria-hidden>\n {formatShortcut(shortcut, apple).map((key) => (\n <Kbd key={key} size=\"sm\">\n {key}\n </Kbd>\n ))}\n </span>\n ) : null}\n </>\n );\n return (\n <Comp ref={element} aria-current={current ? \"page\" : undefined} aria-keyshortcuts={shortcut ? ariaKeyShortcuts(shortcut, apple) : undefined} title={collapsed ? label : undefined} className={s.item({ className })} {...props}>\n {asChild && children ? React.cloneElement(children, undefined, inner) : inner}\n </Comp>\n );\n}\n\n/** The column right of the sidebar: Header, then Main. */\nexport function AppShellBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport type AppShellHeaderProps = Omit<React.ComponentProps<\"header\">, \"title\"> & {\n /** The screen's title (an `h1`). */\n title?: React.ReactNode;\n /** The button that opens the drawer on a phone: its words. */\n menuLabel: string;\n /** The screen's actions, on the right. */\n actions?: React.ReactNode;\n};\n\n/** The top of the screen: the drawer's button (phone), the title, the actions. Sticky. */\nexport function AppShellHeader({ title, menuLabel, actions, className, children, ...props }: AppShellHeaderProps) {\n const { open, setOpen, desktop } = useShell();\n const drag = desktop ? { \"data-tauri-drag-region\": \"\" } : {};\n return (\n <header className={s.header({ className })} {...drag} {...props}>\n <button type=\"button\" className={s.menuButton()} aria-label={menuLabel} aria-expanded={open} aria-haspopup=\"dialog\" onClick={() => setOpen(true)}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M4 7h16M4 12h16M4 17h10\" />\n </svg>\n </button>\n {title && (\n <h1 className={s.title()} {...drag}>\n {title}\n </h1>\n )}\n {children}\n {actions && <div className={s.actions()}>{actions}</div>}\n </header>\n );\n}\n\nexport type AppShellSearchProps = Omit<React.ComponentProps<\"button\">, \"children\" | \"onClick\"> & {\n /** The words (\"Search\", \"Search creators\"): the accessible name, shown beside the keys on a desktop. */\n label: string;\n /** Called when the button is pressed or the shortcut is typed: open your command palette (`CommandDialog`) here. */\n onOpen: () => void;\n /**\n * The shortcut that opens it (a combination); shown on its keys.\n * @default \"mod+k\"\n */\n shortcut?: string;\n};\n\n/** The header's search: a button that looks like a field (a magnifier, its words, ⌘K) and opens the app's command palette. */\nexport function AppShellSearch({ label, onOpen, shortcut = \"mod+k\", className, ...props }: AppShellSearchProps) {\n const apple = useApple();\n useShortcuts({ [shortcut]: () => onOpen() });\n return (\n <button type=\"button\" className={s.search({ className })} aria-keyshortcuts={ariaKeyShortcuts(shortcut, apple)} onClick={onOpen} {...props}>\n <SearchGlyph />\n <span className={s.searchLabel()}>{label}</span>\n <span className={s.searchKeys()} aria-hidden>\n {formatShortcut(shortcut, apple).map((key) => (\n <Kbd key={key} size=\"sm\">\n {key}\n </Kbd>\n ))}\n </span>\n </button>\n );\n}\n\n/** The screen's content. */\nexport function AppShellMain({ className, ...props }: React.ComponentProps<\"main\">) {\n return <main id=\"content\" tabIndex={-1} className={s.main({ className })} {...props} />;\n}\n\nexport const AppShell = {\n Root: AppShellRoot,\n Sidebar: AppShellSidebar,\n Group: AppShellGroup,\n Item: AppShellItem,\n Body: AppShellBody,\n Header: AppShellHeader,\n Search: AppShellSearch,\n Main: AppShellMain,\n};\n","// Server-safe. A key, or a shortcut of several (`<Kbd>⌘</Kbd><Kbd>K</Kbd>`).\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const kbdVariants = tv({\n base:\n \"inline-flex shrink-0 items-center justify-center rounded-md border border-border-default border-b-2 bg-surface-2 \" +\n \"font-mono font-semibold text-fg-secondary\",\n variants: { size: { sm: \"h-5 min-w-5 px-1 text-[10px]\", md: \"h-6 min-w-6 px-1.5 text-xs\" } },\n defaultVariants: { size: \"md\" },\n});\n\nexport type KbdProps = React.ComponentProps<\"kbd\"> & {\n /**\n * sm · md.\n * @default \"md\"\n */\n size?: VariantProps<typeof kbdVariants>[\"size\"];\n};\n\nexport function Kbd({ size, className, ...props }: KbdProps) {\n return <kbd className={kbdVariants({ size, className })} {...props} />;\n}\n","// Keyboard shortcuts for an application: a combination (`mod+k`, `shift+/`, `alt+arrowup`) or a sequence (`g d`: press G,\n// then D). `mod` is ⌘ on Apple devices and Ctrl elsewhere. Plain keys and sequences stay out of text fields; a combination\n// with `mod` works in an input (not in a rich-text editor, where ⌘B is bold). Nothing is decided here about the words.\nimport * as React from \"react\";\n\nimport { useLatest } from \"../media\";\n\nexport type ShortcutMap = Readonly<Record<string, ((event: KeyboardEvent) => void) | undefined>>;\n\nconst isApple = () => typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n\nconst ALIASES: Record<string, string> = { esc: \"escape\", return: \"enter\", space: \" \", spacebar: \" \", up: \"arrowup\", down: \"arrowdown\", left: \"arrowleft\", right: \"arrowright\", del: \"delete\", plus: \"+\" };\ntype Modifier = \"mod\" | \"ctrl\" | \"alt\" | \"shift\" | \"meta\";\n\ntype Combo = { key: string; mod: boolean; ctrl: boolean; alt: boolean; shift: boolean; meta: boolean };\n\n/** `\"mod+shift+k\"` → its parts. A lone `+` key is written `plus`. */\nexport function parseCombo(combo: string): Combo {\n const parts = combo.toLowerCase().split(\"+\").filter(Boolean);\n const key = ALIASES[parts.at(-1) ?? \"\"] ?? parts.at(-1) ?? \"\";\n const has = (name: Modifier) => parts.slice(0, -1).includes(name);\n return { key, mod: has(\"mod\"), ctrl: has(\"ctrl\"), alt: has(\"alt\"), shift: has(\"shift\"), meta: has(\"meta\") };\n}\n\n/** Whether a key event is this combination: its modifiers exactly (`mod` is ⌘ or Ctrl by platform), then its key. */\nexport function matchesCombo(event: Pick<KeyboardEvent, \"key\" | \"metaKey\" | \"ctrlKey\" | \"altKey\" | \"shiftKey\">, combo: string, apple = isApple()): boolean {\n const want = parseCombo(combo);\n const meta = want.meta || (want.mod && apple);\n const ctrl = want.ctrl || (want.mod && !apple);\n if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== want.alt) return false;\n // Shift is only checked when the binding names it: `?` and `/` need it on most layouts.\n if (want.shift && !event.shiftKey) return false;\n return event.key.toLowerCase() === want.key;\n}\n\n/** The keys of a shortcut to show on a keycap: `[\"⌘\", \"K\"]` on a Mac, `[\"Ctrl\", \"K\"]` elsewhere; a sequence `g d` is `[\"G\", \"D\"]`. */\nexport function formatShortcut(shortcut: string, apple = isApple()): string[] {\n if (shortcut.includes(\" \")) return shortcut.split(/\\s+/).map((key) => key.toUpperCase());\n const parts = shortcut.split(\"+\").filter(Boolean);\n const key = parts.at(-1) ?? \"\";\n const names: Record<string, [string, string]> = { mod: [\"⌘\", \"Ctrl\"], meta: [\"⌘\", \"Win\"], ctrl: [\"⌃\", \"Ctrl\"], alt: [\"⌥\", \"Alt\"], shift: [\"⇧\", \"Shift\"] };\n const shown = parts.slice(0, -1).map((part) => names[part.toLowerCase()]?.[apple ? 0 : 1] ?? part);\n const glyph: Record<string, string> = { escape: \"Esc\", enter: \"↵\", arrowup: \"↑\", arrowdown: \"↓\", arrowleft: \"←\", arrowright: \"→\", \" \": \"Space\" };\n const last = glyph[ALIASES[key.toLowerCase()] ?? key.toLowerCase()] ?? key.toUpperCase();\n return [...shown, last];\n}\n\n/**\n * The accelerator of a combination for a desktop app's native menu bar (Tauri's `MenuItem`, Electron's — ADR-002):\n * `mod+k` → `CmdOrCtrl+K`, `shift+mod+/` → `Shift+CmdOrCtrl+/`. The menu and the shell then answer the same keys, and the\n * menu shows them. A sequence (`g d`) has none: a menu item takes one chord.\n */\nexport function acceleratorOf(shortcut: string): string | undefined {\n if (shortcut.trim().includes(\" \")) return undefined;\n const parts = shortcut.split(\"+\").filter(Boolean);\n const names: Record<string, string> = { mod: \"CmdOrCtrl\", meta: \"Super\", ctrl: \"Ctrl\", alt: \"Alt\", shift: \"Shift\" };\n const raw = (parts.at(-1) ?? \"\").toLowerCase();\n const key = ALIASES[raw] ?? raw;\n const keys: Record<string, string> = { escape: \"Escape\", enter: \"Enter\", \" \": \"Space\", arrowup: \"Up\", arrowdown: \"Down\", arrowleft: \"Left\", arrowright: \"Right\", delete: \"Delete\", backspace: \"Backspace\", tab: \"Tab\", \"+\": \"Plus\" };\n const last = keys[key] ?? (key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1));\n return [...parts.slice(0, -1).map((part) => names[part.toLowerCase()] ?? part), last].join(\"+\");\n}\n\nconst noop = () => () => undefined;\n\n/** Whether the person is on an Apple device — `false` on the server and in the first client render (no mismatch), then right. */\nexport function useApple(): boolean {\n return React.useSyncExternalStore(noop, isApple, () => false);\n}\n\n/** The `aria-keyshortcuts` of a combination (`Control+K`, `Meta+K` on a Mac); a sequence has none (it is not a chord). */\nexport function ariaKeyShortcuts(shortcut: string, apple = isApple()): string | undefined {\n if (shortcut.includes(\" \")) return undefined;\n const parts = shortcut.split(\"+\").filter(Boolean);\n const names: Record<string, string> = { mod: apple ? \"Meta\" : \"Control\", meta: \"Meta\", ctrl: \"Control\", alt: \"Alt\", shift: \"Shift\" };\n const key = parts.at(-1) ?? \"\";\n return [...parts.slice(0, -1).map((part) => names[part.toLowerCase()] ?? part), key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)].join(\"+\");\n}\n\n/** Whether a key press happens in something you type in. */\nfunction isEditable(target: EventTarget | null): { field: boolean; rich: boolean } {\n const element = target instanceof HTMLElement ? target : null;\n if (!element) return { field: false, rich: false };\n const rich = element.isContentEditable;\n return { field: rich || /^(input|textarea|select)$/i.test(element.tagName), rich };\n}\n\n/**\n * Binds shortcuts for as long as the component lives (`enabled` turns them off). Each key is a combination or a sequence,\n * each value runs when it is pressed; the handler may change on every render. A shortcut that fires is `preventDefault`ed.\n * A sequence waits `timeout` ms for its next key.\n */\nexport function useShortcuts(map: ShortcutMap, { enabled = true, timeout = 900 }: { enabled?: boolean; timeout?: number } = {}) {\n const latest = useLatest(map);\n const names = Object.keys(map).sort().join(\"\\n\");\n React.useEffect(() => {\n if (!enabled) return;\n const bindings = names.split(\"\\n\").filter(Boolean);\n let buffer: { key: string; at: number }[] = [];\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.defaultPrevented || event.isComposing || event.repeat || !event.key) return;\n const editing = isEditable(event.target);\n const chord = event.metaKey || event.ctrlKey || event.altKey;\n const now = Date.now();\n buffer = [...buffer.filter((entry) => now - entry.at <= timeout), { key: event.key.toLowerCase(), at: now }].slice(-4);\n for (const binding of bindings) {\n const handler = latest.current[binding];\n if (!handler) continue;\n const keys = binding.toLowerCase().split(/\\s+/).map((key) => ALIASES[key] ?? key);\n const fired =\n keys.length > 1\n ? !chord && !editing.field && buffer.length >= keys.length && keys.every((key, i) => buffer[buffer.length - keys.length + i]?.key === key)\n : matchesCombo(event, binding) && (chord ? !editing.rich : !editing.field);\n if (!fired) continue;\n event.preventDefault();\n buffer = [];\n handler(event);\n return;\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [enabled, names, latest, timeout]);\n}\n","// Shared by the data and media primitives (client): the clock format, the reduced-motion preference, the\n// in-view observer, a debounced callback. Never an entry of its own.\nimport * as React from \"react\";\n\n/** Seconds as a clock: `75` → `1:15`, `3725` → `1:02:05`. Negative or unknown values read `0:00`. */\nexport function formatClock(seconds: number): string {\n const s = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const rest = String(s % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${rest}` : `${m}:${rest}`;\n}\n\n/** Clamps `value` between `min` and `max`. */\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst REDUCE = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduce = (notify: () => void) => {\n if (typeof window === \"undefined\" || !window.matchMedia) return () => undefined;\n const query = window.matchMedia(REDUCE);\n query.addEventListener?.(\"change\", notify);\n return () => query.removeEventListener?.(\"change\", notify);\n};\n\n/** `true` when the person asked for less motion (`prefers-reduced-motion: reduce`); follows a change of the setting. */\nexport function usePrefersReducedMotion(): boolean {\n return React.useSyncExternalStore(\n subscribeReduce,\n () => (typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(REDUCE).matches : false),\n () => false,\n );\n}\n\n/** The latest value of a callback, for an effect or an event that must not re-subscribe when it changes. */\nexport function useLatest<T>(value: T) {\n const ref = React.useRef(value);\n React.useLayoutEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n\n/** Calls `onChange(visible)` when at least `threshold` of the element enters or leaves the viewport. */\nexport function useInView(ref: React.RefObject<Element | null>, onChange: (visible: boolean) => void, enabled = true, threshold = 0.25) {\n const latest = useLatest(onChange);\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(([entry]) => entry && latest.current(entry.isIntersecting), { threshold });\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled, threshold, latest]);\n}\n\n/** A value controlled by a prop when it is given, held inside otherwise; `set` calls `onChange` either way. */\nexport function useControllable<T>(value: T | undefined, defaultValue: T, onChange?: (next: T) => void) {\n const [inner, setInner] = React.useState(defaultValue);\n const controlled = value !== undefined;\n const current = controlled ? value : inner;\n const set = React.useCallback(\n (next: T) => {\n if (!controlled) setInner(next);\n onChange?.(next);\n },\n [controlled, onChange],\n );\n return [current, set] as const;\n}\n\n/** Starts a media element without an unhandled rejection (autoplay refused, the source replaced meanwhile). */\nexport function playSafely(media: HTMLMediaElement | null | undefined) {\n try {\n const started = media?.play() as Promise<void> | undefined;\n started?.catch?.(() => undefined);\n } catch {\n // Not playable here (no media support): the controls stay as they are.\n }\n}\n"],"mappings":"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","names":["React","tv","React","key","jsx","tv","value"]}
package/dist/badge.d.ts CHANGED
@@ -18,7 +18,7 @@ declare const badgeVariants: tailwind_variants.TVReturnType<{
18
18
  readonly pulse: {
19
19
  readonly true: "[&>[data-dot]]:motion-safe:animate-pulse";
20
20
  };
21
- }, undefined, "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full font-semibold uppercase tracking-wider", {
21
+ }, undefined, "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-sm font-semibold ", {
22
22
  readonly tone: {
23
23
  readonly neutral: "bg-surface-3 text-fg-secondary";
24
24
  readonly accent: "bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent";
package/dist/badge.js CHANGED
@@ -7,7 +7,7 @@ import { tv } from "tailwind-variants";
7
7
 
8
8
  // src/badge/recipe.ts
9
9
  var badgeRecipe = defineRecipe({
10
- base: "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full font-semibold uppercase tracking-wider",
10
+ base: "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-sm font-semibold ",
11
11
  variants: {
12
12
  tone: {
13
13
  neutral: "bg-surface-3 text-fg-secondary",
package/dist/badge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/badge/badge.tsx","../src/badge/recipe.ts"],"sourcesContent":["// Server-safe: no hook, no context.\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { badgeRecipe } from \"./recipe\";\n\n// Status tones are soft (a tint, a coloured dot, a text role): the invariant status tokens are too light to carry\n// small text on a light surface, or to sit under white text (WCAG AA).\nexport const badgeVariants = tv(badgeRecipe);\n\nexport type BadgeVariants = VariantProps<typeof badgeVariants>;\n\nexport type BadgeProps = React.ComponentProps<\"span\"> &\n BadgeVariants & {\n /**\n * neutral · accent · success · warning · danger (soft tints, the text stays a text role) · scrim (on a media).\n * @default \"neutral\"\n */\n tone?: BadgeVariants[\"tone\"];\n /**\n * sm · md.\n * @default \"sm\"\n */\n size?: BadgeVariants[\"size\"];\n /** Makes the `dot` pulse (stopped under reduced motion). */\n pulse?: BadgeVariants[\"pulse\"];\n /** A small dot before the text, in the tone's colour (pulsing with `pulse`). */\n dot?: boolean;\n };\n\nexport function Badge({ tone, size, pulse, dot, className, children, ...props }: BadgeProps) {\n return (\n <span data-tone={tone ?? \"neutral\"} className={badgeVariants({ tone, size, pulse, className })} {...props}>\n {dot && <span data-dot=\"\" aria-hidden className=\"h-1.5 w-1.5 rounded-full bg-current\" />}\n {children}\n </span>\n );\n}\n","// The badge's tones and sizes, written once as classes of the Krizaka tokens: the web wears them with `tv(badgeRecipe)`,\n// React Native reads the same classes into styles (native/badge.tsx). Status tones are soft (a tint, a coloured dot, a\n// text role): the invariant status tokens are too light to carry small text on a light surface, or to sit under white\n// text (WCAG AA).\nimport { defineRecipe } from \"../recipes/define\";\n\nexport const badgeRecipe = defineRecipe({\n base: \"inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full font-semibold uppercase tracking-wider\",\n variants: {\n tone: {\n neutral: \"bg-surface-3 text-fg-secondary\",\n accent: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent\",\n success: \"bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success\",\n warning: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning\",\n danger: \"bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger\",\n /** On a media: veil and text invariant, identical in both themes. */\n scrim: \"bg-scrim text-fg-on-media backdrop-blur-md\",\n },\n size: { sm: \"px-2 py-0.5 text-[10px]\", md: \"px-2.5 py-1 text-[11px]\" },\n pulse: { true: \"[&>[data-dot]]:motion-safe:animate-pulse\" },\n },\n defaultVariants: { tone: \"neutral\", size: \"sm\" },\n});\n"],"mappings":";;;;;AAEA,SAAS,UAA6B;;;ACI/B,IAAM,cAAc,aAAa;AAAA,EACtC,MAAM;AAAA,EACN,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA;AAAA,MAER,OAAO;AAAA,IACT;AAAA,IACA,MAAM,EAAE,IAAI,2BAA2B,IAAI,0BAA0B;AAAA,IACrE,OAAO,EAAE,MAAM,2CAA2C;AAAA,EAC5D;AAAA,EACA,iBAAiB,EAAE,MAAM,WAAW,MAAM,KAAK;AACjD,CAAC;;;ADUG,SACU,KADV;AAxBG,IAAM,gBAAgB,GAAG,WAAW;AAsBpC,SAAS,MAAM,EAAE,MAAM,MAAM,OAAO,KAAK,WAAW,UAAU,GAAG,MAAM,GAAe;AAC3F,SACE,qBAAC,UAAK,aAAW,QAAQ,WAAW,WAAW,cAAc,EAAE,MAAM,MAAM,OAAO,UAAU,CAAC,GAAI,GAAG,OACjG;AAAA,WAAO,oBAAC,UAAK,YAAS,IAAG,eAAW,MAAC,WAAU,uCAAsC;AAAA,IACrF;AAAA,KACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/badge/badge.tsx","../src/badge/recipe.ts"],"sourcesContent":["// Server-safe: no hook, no context.\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { badgeRecipe } from \"./recipe\";\n\n// Status tones are soft (a tint, a coloured dot, a text role): the invariant status tokens are too light to carry\n// small text on a light surface, or to sit under white text (WCAG AA).\nexport const badgeVariants = tv(badgeRecipe);\n\nexport type BadgeVariants = VariantProps<typeof badgeVariants>;\n\nexport type BadgeProps = React.ComponentProps<\"span\"> &\n BadgeVariants & {\n /**\n * neutral · accent · success · warning · danger (soft tints, the text stays a text role) · scrim (on a media).\n * @default \"neutral\"\n */\n tone?: BadgeVariants[\"tone\"];\n /**\n * sm · md.\n * @default \"sm\"\n */\n size?: BadgeVariants[\"size\"];\n /** Makes the `dot` pulse (stopped under reduced motion). */\n pulse?: BadgeVariants[\"pulse\"];\n /** A small dot before the text, in the tone's colour (pulsing with `pulse`). */\n dot?: boolean;\n };\n\nexport function Badge({ tone, size, pulse, dot, className, children, ...props }: BadgeProps) {\n return (\n <span data-tone={tone ?? \"neutral\"} className={badgeVariants({ tone, size, pulse, className })} {...props}>\n {dot && <span data-dot=\"\" aria-hidden className=\"h-1.5 w-1.5 rounded-full bg-current\" />}\n {children}\n </span>\n );\n}\n","// The badge's tones and sizes, written once as classes of the Krizaka tokens: the web wears them with `tv(badgeRecipe)`,\n// React Native reads the same classes into styles (native/badge.tsx). Status tones are soft (a tint, a coloured dot, a\n// text role): the invariant status tokens are too light to carry small text on a light surface, or to sit under white\n// text (WCAG AA).\nimport { defineRecipe } from \"../recipes/define\";\n\nexport const badgeRecipe = defineRecipe({\n base: \"inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-sm font-semibold \",\n variants: {\n tone: {\n neutral: \"bg-surface-3 text-fg-secondary\",\n accent: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent\",\n success: \"bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success\",\n warning: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning\",\n danger: \"bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger\",\n /** On a media: veil and text invariant, identical in both themes. */\n scrim: \"bg-scrim text-fg-on-media backdrop-blur-md\",\n },\n size: { sm: \"px-2 py-0.5 text-[10px]\", md: \"px-2.5 py-1 text-[11px]\" },\n pulse: { true: \"[&>[data-dot]]:motion-safe:animate-pulse\" },\n },\n defaultVariants: { tone: \"neutral\", size: \"sm\" },\n});\n"],"mappings":";;;;;AAEA,SAAS,UAA6B;;;ACI/B,IAAM,cAAc,aAAa;AAAA,EACtC,MAAM;AAAA,EACN,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA;AAAA,MAER,OAAO;AAAA,IACT;AAAA,IACA,MAAM,EAAE,IAAI,2BAA2B,IAAI,0BAA0B;AAAA,IACrE,OAAO,EAAE,MAAM,2CAA2C;AAAA,EAC5D;AAAA,EACA,iBAAiB,EAAE,MAAM,WAAW,MAAM,KAAK;AACjD,CAAC;;;ADUG,SACU,KADV;AAxBG,IAAM,gBAAgB,GAAG,WAAW;AAsBpC,SAAS,MAAM,EAAE,MAAM,MAAM,OAAO,KAAK,WAAW,UAAU,GAAG,MAAM,GAAe;AAC3F,SACE,qBAAC,UAAK,aAAW,QAAQ,WAAW,WAAW,cAAc,EAAE,MAAM,MAAM,OAAO,UAAU,CAAC,GAAI,GAAG,OACjG;AAAA,WAAO,oBAAC,UAAK,YAAS,IAAG,eAAW,MAAC,WAAU,uCAAsC;AAAA,IACrF;AAAA,KACH;AAEJ;","names":[]}