@devalok/shilp-sutra 0.52.0 → 0.53.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 (41) hide show
  1. package/MIGRATION.md +6 -0
  2. package/dist/_chunks/motion-provider.js +7 -6
  3. package/dist/_chunks/motion-provider.js.map +1 -1
  4. package/dist/_chunks/success.js +53 -53
  5. package/dist/ai/command-bar.js +158 -158
  6. package/dist/ai/command-bar.js.map +1 -1
  7. package/dist/ai/conversation.js +5 -5
  8. package/dist/composed/command-palette.js +17 -17
  9. package/dist/composed/command-palette.js.map +1 -1
  10. package/dist/composed/priority-indicator.d.ts +15 -6
  11. package/dist/composed/priority-indicator.d.ts.map +1 -1
  12. package/dist/composed/priority-indicator.js +37 -88
  13. package/dist/composed/priority-indicator.js.map +1 -1
  14. package/dist/composed/schedule-view.d.ts +17 -2
  15. package/dist/composed/schedule-view.d.ts.map +1 -1
  16. package/dist/composed/schedule-view.js +163 -64
  17. package/dist/composed/schedule-view.js.map +1 -1
  18. package/dist/motion/motion-provider.d.ts.map +1 -1
  19. package/dist/shell/bottom-navbar.d.ts +32 -6
  20. package/dist/shell/bottom-navbar.d.ts.map +1 -1
  21. package/dist/shell/bottom-navbar.js +156 -129
  22. package/dist/shell/bottom-navbar.js.map +1 -1
  23. package/dist/tokens/primitives.css +6 -2
  24. package/dist/tokens/utilities.css +6 -0
  25. package/dist/ui/autocomplete.d.ts +34 -32
  26. package/dist/ui/autocomplete.d.ts.map +1 -1
  27. package/dist/ui/autocomplete.js +115 -110
  28. package/dist/ui/autocomplete.js.map +1 -1
  29. package/dist/ui/combobox.js +4 -4
  30. package/dist/ui/index.js +30 -30
  31. package/dist/ui/search-input.js +4 -4
  32. package/dist/ui/sidebar.js +7 -7
  33. package/docs/components/composed/priority-indicator.md +22 -11
  34. package/docs/components/composed/schedule-view.md +20 -5
  35. package/docs/components/shell/bottom-navbar.md +24 -5
  36. package/docs/components/ui/autocomplete.md +26 -10
  37. package/llms.txt +1 -1
  38. package/mcp-manifest.json +179 -26
  39. package/package.json +1 -1
  40. package/skill/SKILL.md +1 -1
  41. package/skill/references/components.md +1 -1
@@ -1,106 +1,55 @@
1
1
  "use client";
2
- import { cva as e } from "../_chunks/vendor-utils.js";
3
- import { cn as t } from "../ui/lib/utils.js";
4
- import { Icon as n } from "../ui/icon.js";
5
- import * as r from "react";
6
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
7
- import { IconAlertTriangle as o, IconArrowDown as s, IconArrowUp as c, IconMinus as l } from "@tabler/icons-react";
8
- import { motion as u } from "framer-motion";
2
+ import { BadgeCompound as e } from "../_chunks/badge-group.js";
3
+ import * as t from "react";
4
+ import { jsx as n } from "react/jsx-runtime";
5
+ import { IconAlertTriangle as r, IconArrowDown as i, IconArrowUp as a, IconMinus as o } from "@tabler/icons-react";
9
6
  //#region src/composed/priority-indicator.tsx
10
- var d = {
7
+ var s = {
11
8
  LOW: {
12
- icon: s,
13
- color: "text-category-slate-11",
14
- bgColor: "bg-category-slate-3",
9
+ icon: i,
10
+ color: "slate",
11
+ variant: "soft",
15
12
  label: "Low"
16
13
  },
17
14
  MEDIUM: {
18
- icon: l,
19
- color: "text-warning-11",
20
- bgColor: "bg-warning-3",
15
+ icon: o,
16
+ color: "warning",
17
+ variant: "soft",
21
18
  label: "Medium"
22
19
  },
23
20
  HIGH: {
24
- icon: c,
25
- color: "text-error-11",
26
- bgColor: "bg-error-3",
21
+ icon: a,
22
+ color: "error",
23
+ variant: "soft",
27
24
  label: "High"
28
25
  },
29
26
  URGENT: {
30
- icon: o,
31
- color: "text-error-11",
32
- bgColor: "bg-error-3",
27
+ icon: r,
28
+ color: "error",
29
+ variant: "solid",
33
30
  label: "Urgent"
34
31
  }
35
- }, f = e("inline-flex items-center gap-ds-02b font-body", {
36
- variants: { display: {
37
- compact: "",
38
- full: ""
39
- } },
40
- defaultVariants: { display: "full" }
41
- }), p = r.forwardRef(({ priority: e, display: r, className: o, ...s }, c) => {
42
- let l = e.toUpperCase(), p = d[l], m = p.icon, h = l === "URGENT";
43
- if (r === "compact") {
44
- let e = /* @__PURE__ */ i("div", {
45
- ref: c,
46
- className: t("inline-flex items-center justify-center rounded-control p-ds-02", p.bgColor, o),
47
- title: p.label,
48
- ...s,
49
- children: /* @__PURE__ */ i(n, {
50
- icon: m,
51
- size: "sm",
52
- className: p.color
53
- })
54
- });
55
- return h ? /* @__PURE__ */ i(u.div, {
56
- animate: { scale: [
57
- 1,
58
- 1.1,
59
- 1
60
- ] },
61
- transition: {
62
- repeat: Infinity,
63
- duration: 2,
64
- ease: "easeInOut"
65
- },
66
- className: "inline-flex",
67
- children: e
68
- }) : e;
69
- }
70
- return /* @__PURE__ */ a("div", {
32
+ }, c = t.forwardRef(({ priority: t, iconOnly: r, display: i, children: a, ...o }, c) => {
33
+ let l = s[t.toUpperCase()] ?? s.MEDIUM, u = r ?? i === "compact", d = a ?? l.label;
34
+ return u ? /* @__PURE__ */ n(e, {
35
+ ref: c,
36
+ ...o,
37
+ variant: l.variant,
38
+ color: l.color,
39
+ size: "sm",
40
+ startIcon: l.icon,
41
+ role: "img",
42
+ "aria-label": typeof d == "string" ? d : l.label
43
+ }) : /* @__PURE__ */ n(e, {
71
44
  ref: c,
72
- className: t(f({ display: r }), o),
73
- ...s,
74
- children: [h ? /* @__PURE__ */ i(u.div, {
75
- animate: { scale: [
76
- 1,
77
- 1.1,
78
- 1
79
- ] },
80
- transition: {
81
- repeat: Infinity,
82
- duration: 2,
83
- ease: "easeInOut"
84
- },
85
- className: t("inline-flex items-center justify-center rounded-control p-ds-01", p.bgColor),
86
- children: /* @__PURE__ */ i(n, {
87
- icon: m,
88
- size: "sm",
89
- className: p.color
90
- })
91
- }) : /* @__PURE__ */ i("div", {
92
- className: t("inline-flex items-center justify-center rounded-control p-ds-01", p.bgColor),
93
- children: /* @__PURE__ */ i(n, {
94
- icon: m,
95
- size: "sm",
96
- className: p.color
97
- })
98
- }), /* @__PURE__ */ i("span", {
99
- className: "text-body-sm text-surface-fg-muted",
100
- children: p.label
101
- })]
45
+ ...o,
46
+ variant: l.variant,
47
+ color: l.color,
48
+ size: "sm",
49
+ startIcon: l.icon,
50
+ children: d
102
51
  });
103
52
  });
104
- p.displayName = "PriorityIndicator";
53
+ c.displayName = "PriorityIndicator";
105
54
  //#endregion
106
- export { p as PriorityIndicator };
55
+ export { c as PriorityIndicator };
@@ -1 +1 @@
1
- {"version":3,"file":"priority-indicator.js","names":[],"sources":["../../src/composed/priority-indicator.tsx"],"sourcesContent":["'use client'\n\nimport type { Icon as TablerIcon } from '@tabler/icons-react'\nimport {\n IconAlertTriangle,\n IconArrowDown,\n IconArrowUp,\n IconMinus,\n} from '@tabler/icons-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon, type IconProps } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\n\nexport type Priority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT' | 'low' | 'medium' | 'high' | 'urgent'\n\nconst priorityConfig: Record<\n Uppercase<Priority>,\n { icon: TablerIcon; color: string; bgColor: string; label: string }\n> = {\n LOW: {\n icon: IconArrowDown,\n color: 'text-category-slate-11',\n bgColor: 'bg-category-slate-3',\n label: 'Low',\n },\n MEDIUM: {\n icon: IconMinus,\n color: 'text-warning-11',\n bgColor: 'bg-warning-3',\n label: 'Medium',\n },\n HIGH: {\n icon: IconArrowUp,\n color: 'text-error-11',\n bgColor: 'bg-error-3',\n label: 'High',\n },\n URGENT: {\n icon: IconAlertTriangle,\n color: 'text-error-11',\n bgColor: 'bg-error-3',\n label: 'Urgent',\n },\n}\n\nconst priorityVariants = cva(\n 'inline-flex items-center gap-ds-02b font-body',\n {\n variants: {\n display: {\n compact: '',\n full: '',\n },\n },\n defaultVariants: {\n display: 'full',\n },\n },\n)\n\nexport interface PriorityIndicatorProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof priorityVariants> {\n priority: Priority\n}\n\nconst PriorityIndicator = React.forwardRef<HTMLDivElement, PriorityIndicatorProps>(\n ({ priority, display, className, ...props }, ref) => {\n const normalizedPriority = priority.toUpperCase() as 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'\n const config = priorityConfig[normalizedPriority]\n const PriorityIcon = config.icon as IconProps['icon']\n\n const isUrgent = normalizedPriority === 'URGENT'\n\n if (display === 'compact') {\n const iconWrapper = (\n <div\n ref={ref}\n className={cn(\n 'inline-flex items-center justify-center rounded-control p-ds-02',\n config.bgColor,\n className,\n )}\n title={config.label}\n {...props}\n >\n <Icon icon={PriorityIcon} size=\"sm\" className={config.color} />\n </div>\n )\n\n if (isUrgent) {\n return (\n <motion.div\n animate={{ scale: [1, 1.1, 1] }}\n transition={{ repeat: Infinity, duration: 2, ease: 'easeInOut' }}\n className=\"inline-flex\"\n >\n {iconWrapper}\n </motion.div>\n )\n }\n\n return iconWrapper\n }\n\n return (\n <div\n ref={ref}\n className={cn(priorityVariants({ display }), className)}\n {...props}\n >\n {isUrgent ? (\n <motion.div\n animate={{ scale: [1, 1.1, 1] }}\n transition={{ repeat: Infinity, duration: 2, ease: 'easeInOut' }}\n className={cn(\n 'inline-flex items-center justify-center rounded-control p-ds-01',\n config.bgColor,\n )}\n >\n <Icon icon={PriorityIcon} size=\"sm\" className={config.color} />\n </motion.div>\n ) : (\n <div\n className={cn(\n 'inline-flex items-center justify-center rounded-control p-ds-01',\n config.bgColor,\n )}\n >\n <Icon icon={PriorityIcon} size=\"sm\" className={config.color} />\n </div>\n )}\n <span className=\"text-body-sm text-surface-fg-muted\">\n {config.label}\n </span>\n </div>\n )\n },\n)\nPriorityIndicator.displayName = 'PriorityIndicator'\n\nexport { PriorityIndicator }\n"],"mappings":";;;;;;;;;AAkBA,IAAM,IAGF;CACF,KAAK;EACH,MAAM;EACN,OAAO;EACP,SAAS;EACT,OAAO;CACT;CACA,QAAQ;EACN,MAAM;EACN,OAAO;EACP,SAAS;EACT,OAAO;CACT;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,SAAS;EACT,OAAO;CACT;CACA,QAAQ;EACN,MAAM;EACN,OAAO;EACP,SAAS;EACT,OAAO;CACT;AACF,GAEM,IAAmB,EACvB,iDACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,MAAM;CACR,EACF;CACA,iBAAiB,EACf,SAAS,OACX;AACF,CACF,GAQM,IAAoB,EAAM,YAC7B,EAAE,aAAU,YAAS,cAAW,GAAG,KAAS,MAAQ;CACnD,IAAM,IAAqB,EAAS,YAAY,GAC1C,IAAS,EAAe,IACxB,IAAe,EAAO,MAEtB,IAAW,MAAuB;CAExC,IAAI,MAAY,WAAW;EACzB,IAAM,IACJ,kBAAC,OAAD;GACO;GACL,WAAW,EACT,mEACA,EAAO,SACP,CACF;GACA,OAAO,EAAO;GACd,GAAI;aAEJ,kBAAC,GAAD;IAAM,MAAM;IAAc,MAAK;IAAK,WAAW,EAAO;GAAQ,CAAA;EAC3D,CAAA;EAeP,OAZI,IAEA,kBAAC,EAAO,KAAR;GACE,SAAS,EAAE,OAAO;IAAC;IAAG;IAAK;GAAC,EAAE;GAC9B,YAAY;IAAE,QAAQ;IAAU,UAAU;IAAG,MAAM;GAAY;GAC/D,WAAU;aAET;EACS,CAAA,IAIT;CACT;CAEA,OACE,kBAAC,OAAD;EACO;EACL,WAAW,EAAG,EAAiB,EAAE,WAAQ,CAAC,GAAG,CAAS;EACtD,GAAI;YAHN,CAKG,IACC,kBAAC,EAAO,KAAR;GACE,SAAS,EAAE,OAAO;IAAC;IAAG;IAAK;GAAC,EAAE;GAC9B,YAAY;IAAE,QAAQ;IAAU,UAAU;IAAG,MAAM;GAAY;GAC/D,WAAW,EACT,mEACA,EAAO,OACT;aAEA,kBAAC,GAAD;IAAM,MAAM;IAAc,MAAK;IAAK,WAAW,EAAO;GAAQ,CAAA;EACpD,CAAA,IAEZ,kBAAC,OAAD;GACE,WAAW,EACT,mEACA,EAAO,OACT;aAEA,kBAAC,GAAD;IAAM,MAAM;IAAc,MAAK;IAAK,WAAW,EAAO;GAAQ,CAAA;EAC3D,CAAA,GAEP,kBAAC,QAAD;GAAM,WAAU;aACb,EAAO;EACJ,CAAA,CACH;;AAET,CACF;AACA,EAAkB,cAAc"}
1
+ {"version":3,"file":"priority-indicator.js","names":[],"sources":["../../src/composed/priority-indicator.tsx"],"sourcesContent":["import {\n IconAlertTriangle,\n IconArrowDown,\n IconArrowUp,\n IconMinus,\n} from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Badge, type BadgeColor, type BadgeProps } from '../ui/badge'\nimport type { IconInput } from '../ui/lib/icon-input'\n\nexport type Priority =\n | 'LOW'\n | 'MEDIUM'\n | 'HIGH'\n | 'URGENT'\n | 'low'\n | 'medium'\n | 'high'\n | 'urgent'\n\n/**\n * Severity is conveyed by **icon + color + weight**, never motion. URGENT uses\n * a solid fill so the top tier reads at a glance without any animation\n * (matches Linear/Jira; also removes the prior WCAG 2.2.2 infinite-pulse issue).\n */\nconst priorityConfig: Record<\n 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT',\n { icon: IconInput; color: BadgeColor; variant: NonNullable<BadgeProps['variant']>; label: string }\n> = {\n LOW: { icon: IconArrowDown, color: 'slate', variant: 'soft', label: 'Low' },\n MEDIUM: { icon: IconMinus, color: 'warning', variant: 'soft', label: 'Medium' },\n HIGH: { icon: IconArrowUp, color: 'error', variant: 'soft', label: 'High' },\n URGENT: { icon: IconAlertTriangle, color: 'error', variant: 'solid', label: 'Urgent' },\n}\n\nexport interface PriorityIndicatorProps extends React.HTMLAttributes<HTMLElement> {\n priority: Priority\n /** Render an icon-only chip (no visible text). Set an accessible name is handled automatically. */\n iconOnly?: boolean\n /**\n * @deprecated Use `iconOnly`. `'compact'` → icon-only, `'full'` → labeled.\n * Accepted for back-compat; maps to `iconOnly`.\n */\n display?: 'compact' | 'full'\n /** Override the label text (i18n / custom copy). Defaults to the priority name. */\n children?: React.ReactNode\n}\n\n/**\n * Presentational priority chip — composes `Badge` so radius, color semantics,\n * a11y labeling, and reduced-motion handling come from one place.\n */\nconst PriorityIndicator = React.forwardRef<HTMLElement, PriorityIndicatorProps>(\n ({ priority, iconOnly, display, children, ...props }, ref) => {\n const key = priority.toUpperCase() as 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'\n // Defensive fallback: an unexpected priority string renders as MEDIUM\n // rather than throwing on `config.icon`.\n const config = priorityConfig[key] ?? priorityConfig.MEDIUM\n const compact = iconOnly ?? display === 'compact'\n const label = children ?? config.label\n\n if (compact) {\n return (\n <Badge\n ref={ref}\n {...props}\n variant={config.variant}\n color={config.color}\n size=\"sm\"\n startIcon={config.icon}\n role=\"img\"\n aria-label={typeof label === 'string' ? label : config.label}\n />\n )\n }\n\n return (\n <Badge\n ref={ref}\n {...props}\n variant={config.variant}\n color={config.color}\n size=\"sm\"\n startIcon={config.icon}\n >\n {label}\n </Badge>\n )\n },\n)\nPriorityIndicator.displayName = 'PriorityIndicator'\n\nexport { PriorityIndicator }\n"],"mappings":";;;;;AA0BA,IAAM,IAGF;CACF,KAAK;EAAE,MAAM;EAAe,OAAO;EAAS,SAAS;EAAQ,OAAO;CAAM;CAC1E,QAAQ;EAAE,MAAM;EAAW,OAAO;EAAW,SAAS;EAAQ,OAAO;CAAS;CAC9E,MAAM;EAAE,MAAM;EAAa,OAAO;EAAS,SAAS;EAAQ,OAAO;CAAO;CAC1E,QAAQ;EAAE,MAAM;EAAmB,OAAO;EAAS,SAAS;EAAS,OAAO;CAAS;AACvF,GAmBM,IAAoB,EAAM,YAC7B,EAAE,aAAU,aAAU,YAAS,aAAU,GAAG,KAAS,MAAQ;CAI5D,IAAM,IAAS,EAHH,EAAS,YAGS,MAAQ,EAAe,QAC/C,IAAU,KAAY,MAAY,WAClC,IAAQ,KAAY,EAAO;CAiBjC,OAfI,IAEA,kBAAC,GAAD;EACO;EACL,GAAI;EACJ,SAAS,EAAO;EAChB,OAAO,EAAO;EACd,MAAK;EACL,WAAW,EAAO;EAClB,MAAK;EACL,cAAY,OAAO,KAAU,WAAW,IAAQ,EAAO;CACxD,CAAA,IAKH,kBAAC,GAAD;EACO;EACL,GAAI;EACJ,SAAS,EAAO;EAChB,OAAO,EAAO;EACd,MAAK;EACL,WAAW,EAAO;YAEjB;CACI,CAAA;AAEX,CACF;AACA,EAAkB,cAAc"}
@@ -9,7 +9,9 @@ export interface ScheduleEvent {
9
9
  }
10
10
  /**
11
11
  * A day/week calendar schedule grid. Renders time-slotted events with
12
- * proportional height, color coding, click handlers, and a current-time indicator.
12
+ * proportional height, color coding, overlapping-event columns, a live
13
+ * current-time indicator, and keyboard-navigable slots (when `onSlotClick` is
14
+ * set). Presentational — drive `events`/`date`/`view` from the parent.
13
15
  */
14
16
  export interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement> {
15
17
  /** Display mode: single-day column or full-week (7 columns). */
@@ -20,7 +22,10 @@ export interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement>
20
22
  events: ScheduleEvent[];
21
23
  /** Called when an event block is clicked. */
22
24
  onEventClick?: (event: ScheduleEvent) => void;
23
- /** Called when an empty time slot is clicked, with the slot's start and end times. */
25
+ /**
26
+ * Called when an empty time slot is clicked. When omitted, slots render as
27
+ * non-interactive grid lines (no keyboard/AT tab stops).
28
+ */
24
29
  onSlotClick?: (start: Date, end: Date) => void;
25
30
  /** First visible hour (default 8). */
26
31
  startHour?: number;
@@ -28,6 +33,16 @@ export interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement>
28
33
  endHour?: number;
29
34
  /** Slot duration in minutes (default 30). */
30
35
  slotDuration?: number;
36
+ /** Currently-selected event id — rendered with a ring. */
37
+ selectedEventId?: string;
38
+ /** Custom event content renderer. Receives the event; return the block body. */
39
+ renderEvent?: (event: ScheduleEvent) => React.ReactNode;
40
+ /** Optional toolbar/header rendered above the grid. */
41
+ header?: React.ReactNode;
42
+ /** Content shown when there are no events in view. */
43
+ emptyState?: React.ReactNode;
44
+ /** Grid body height (CSS length or px number). @default 480 */
45
+ height?: number | string;
31
46
  }
32
47
  declare const ScheduleView: React.ForwardRefExoticComponent<ScheduleViewProps & React.RefAttributes<HTMLDivElement>>;
33
48
  export { ScheduleView };
@@ -1 +1 @@
1
- {"version":3,"file":"schedule-view.d.ts","sourceRoot":"","sources":["../../src/composed/schedule-view.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CACxE;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC7E,gEAAgE;IAChE,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,6EAA6E;IAC7E,IAAI,EAAE,IAAI,CAAA;IACV,8CAA8C;IAC9C,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC7C,sFAAsF;IACtF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,KAAK,IAAI,CAAA;IAC9C,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAqOD,QAAA,MAAM,YAAY,0FAyDjB,CAAA;AAGD,OAAO,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"schedule-view.d.ts","sourceRoot":"","sources":["../../src/composed/schedule-view.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CACxE;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC7E,gEAAgE;IAChE,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,6EAA6E;IAC7E,IAAI,EAAE,IAAI,CAAA;IACV,8CAA8C;IAC9C,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC7C;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,KAAK,IAAI,CAAA;IAC9C,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gFAAgF;IAChF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAA;IACvD,uDAAuD;IACvD,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,sDAAsD;IACtD,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACzB;AA8SD,QAAA,MAAM,YAAY,0FA6HjB,CAAA;AAGD,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -24,71 +24,111 @@ function h(e) {
24
24
  return e === 0 ? "12 AM" : e < 12 ? `${e} AM` : e === 12 ? "12 PM" : `${e - 12} PM`;
25
25
  }
26
26
  function g(e, t, n) {
27
- let r = (n - t) * 60, i = (c(e.start) - t) * 60 + l(e.start), a = o(e.end, e.start), s = i / r * 100, u = a / r * 100;
28
- return {
29
- top: `${s}%`,
30
- height: `${u}%`
27
+ let r = (n - t) * 60, i = [...e].sort((e, t) => e.start.getTime() - t.start.getTime()), a = [], s = [], u = -Infinity, d = () => {
28
+ let e = s.reduce((e, t) => Math.max(e, t.col + 1), 0);
29
+ for (let t of s) t.cols = e;
30
+ a.push(...s), s = [], u = -Infinity;
31
31
  };
32
+ for (let e of i) {
33
+ let n = (c(e.start) - t) * 60 + l(e.start), i = Math.max(o(e.end, e.start), 1), a = e.start.getTime();
34
+ a >= u && s.length > 0 && d();
35
+ let f = new Set(s.filter((e) => e.event.end.getTime() > a).map((e) => e.col)), p = 0;
36
+ for (; f.has(p);) p++;
37
+ s.push({
38
+ event: e,
39
+ top: n / r * 100,
40
+ height: i / r * 100,
41
+ col: p,
42
+ cols: 1
43
+ }), u = Math.max(u, e.end.getTime());
44
+ }
45
+ return s.length > 0 && d(), a;
32
46
  }
33
47
  function _({ startHour: e, endHour: t }) {
34
48
  let n = [];
35
49
  for (let r = e; r < t; r++) n.push(r);
36
50
  let i = 100 / (t - e);
37
51
  return /* @__PURE__ */ r("div", {
38
- className: "relative shrink-0 w-[60px] border-r border-surface-border-strong",
52
+ className: "relative w-ds-11 shrink-0 border-e border-surface-border-strong",
39
53
  "aria-hidden": "true",
40
54
  children: n.map((e) => /* @__PURE__ */ r("div", {
41
- className: "text-caption text-surface-fg-muted pr-ds-02 text-right",
55
+ className: "pe-ds-02 text-end text-caption text-surface-fg-muted",
42
56
  style: { height: `${i}%` },
43
57
  children: h(e)
44
58
  }, e))
45
59
  });
46
60
  }
47
- function v({ date: n, events: a, startHour: o, endHour: f, slotDuration: h, onEventClick: _, onSlotClick: v, showHeader: y }) {
48
- let b = a.filter((e) => u(e.start, n)), x = (f - o) * 60, S = x / h, C = [];
49
- for (let e = 0; e < S; e++) {
50
- let t = new Date(n);
51
- t.setHours(o, e * h, 0, 0);
52
- let r = new Date(t);
53
- r.setMinutes(r.getMinutes() + h), C.push({
54
- start: t,
55
- end: r
56
- });
57
- }
58
- let w = d(n), T = null;
59
- if (w) {
60
- let e = /* @__PURE__ */ new Date(), t = c(e), n = l(e);
61
- t >= o && t < f && (T = ((t - o) * 60 + n) / x * 100);
61
+ function v({ dayIndex: a, date: o, events: f, startHour: h, endHour: _, slotDuration: v, now: y, active: b, interactive: x, onSlotKeyDown: S, selectedEventId: C, renderEvent: w, onEventClick: T, onSlotClick: E, showHeader: D, nowLineRef: O }) {
62
+ let k = n.useMemo(() => g(f.filter((e) => u(e.start, o)), h, _), [
63
+ f,
64
+ o,
65
+ h,
66
+ _
67
+ ]), A = (_ - h) * 60, j = A / v, M = n.useMemo(() => {
68
+ let e = [];
69
+ for (let t = 0; t < j; t++) {
70
+ let n = new Date(o);
71
+ n.setHours(h, t * v, 0, 0);
72
+ let r = new Date(n);
73
+ r.setMinutes(r.getMinutes() + v), e.push({
74
+ start: n,
75
+ end: r
76
+ });
77
+ }
78
+ return e;
79
+ }, [
80
+ o,
81
+ h,
82
+ v,
83
+ j
84
+ ]), N = d(o), P = null;
85
+ if (N) {
86
+ let e = c(y);
87
+ e >= h && e < _ && (P = ((e - h) * 60 + l(y)) / A * 100);
62
88
  }
63
89
  return /* @__PURE__ */ i("div", {
64
- className: "flex flex-1 flex-col min-w-ds-11",
65
- children: [y && /* @__PURE__ */ r("div", {
66
- className: e("text-center text-body-sm font-semibold py-ds-02 border-b border-surface-border-strong", w ? "text-accent-11 bg-accent-2" : "text-surface-fg bg-surface-raised"),
67
- children: s(n, "EEE d")
90
+ className: "flex min-w-ds-11 flex-1 flex-col",
91
+ children: [D && /* @__PURE__ */ r("div", {
92
+ className: e("border-b border-surface-border-strong py-ds-02 text-center text-body-sm font-semibold", N ? "bg-accent-2 text-accent-11" : "bg-surface-raised text-surface-fg"),
93
+ children: s(o, "EEE d")
68
94
  }), /* @__PURE__ */ i("div", {
69
95
  className: "relative flex-1",
70
96
  children: [
71
- C.map((t, n) => /* @__PURE__ */ r("button", {
97
+ M.map((t, n) => x ? /* @__PURE__ */ r("button", {
72
98
  type: "button",
73
- className: e("block w-full border-b border-surface-border hover:bg-surface-raised-hover transition-colors ease-productive-standard", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent-9", n % 2 == 0 ? "border-surface-border-strong" : "border-surface-border-subtle"),
74
- style: { height: `${100 / S}%` },
75
- onClick: () => v?.(t.start, t.end),
76
- "aria-label": `${s(t.start, "h:mm a")} to ${s(t.end, "h:mm a")}`
99
+ "data-day": a,
100
+ "data-slot": n,
101
+ tabIndex: a === b.day && n === b.slot ? 0 : -1,
102
+ className: e("block w-full min-h-ds-06 border-b transition-colors ease-productive-standard duration-fast-02", "hover:bg-surface-raised-hover", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent-9", n % 2 == 0 ? "border-surface-border-strong" : "border-surface-border-subtle"),
103
+ style: { height: `${100 / j}%` },
104
+ onClick: () => E?.(t.start, t.end),
105
+ onKeyDown: S,
106
+ "aria-label": `${s(t.start, "EEE h:mm a")} to ${s(t.end, "h:mm a")}`
107
+ }, n) : /* @__PURE__ */ r("div", {
108
+ "aria-hidden": "true",
109
+ className: e("w-full border-b", n % 2 == 0 ? "border-surface-border-strong" : "border-surface-border-subtle"),
110
+ style: { height: `${100 / j}%` }
77
111
  }, n)),
78
- b.map((t) => {
79
- let n = g(t, o, f), a = t.color ?? "accent", c = p[a], l = m[a];
112
+ k.map(({ event: t, top: n, height: a, col: o, cols: c }) => {
113
+ let l = t.color ?? "accent", u = 100 / c, d = t.id === C;
80
114
  return /* @__PURE__ */ r("button", {
81
115
  type: "button",
82
- className: e("absolute left-ds-01 right-ds-01 rounded-control-inner px-ds-02 py-ds-01", "text-left text-body-xs font-medium overflow-hidden cursor-pointer", "hover:shadow-raised transition-[box-shadow] duration-fast-02 ease-productive-standard", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9", c),
83
- style: n,
116
+ "aria-pressed": d || void 0,
117
+ className: e("absolute overflow-hidden rounded-control-inner px-ds-02 py-ds-01", "cursor-pointer text-start text-body-xs font-medium", "transition-[box-shadow] ease-productive-standard duration-fast-02 hover:shadow-raised", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9", "motion-safe:active:scale-[0.98]", d && "ring-2 ring-accent-9", p[l]),
118
+ style: {
119
+ top: `${n}%`,
120
+ height: `${a}%`,
121
+ insetInlineStart: `calc(${o * u}% + 2px)`,
122
+ width: `calc(${u}% - 4px)`
123
+ },
84
124
  onClick: (e) => {
85
- e.stopPropagation(), _?.(t);
125
+ e.stopPropagation(), T?.(t);
86
126
  },
87
127
  "aria-label": `${t.title}, ${s(t.start, "h:mm a")} to ${s(t.end, "h:mm a")}`,
88
- children: /* @__PURE__ */ i("span", {
128
+ children: w ? w(t) : /* @__PURE__ */ i("span", {
89
129
  className: "flex items-start gap-ds-02",
90
130
  children: [/* @__PURE__ */ r("span", {
91
- className: e("mt-[3px] h-ds-03 w-ds-03 shrink-0 rounded-pill", l),
131
+ className: e("mt-0.5 h-ds-03 w-ds-03 shrink-0 rounded-pill", m[l]),
92
132
  "aria-hidden": "true"
93
133
  }), /* @__PURE__ */ r("span", {
94
134
  className: "line-clamp-2",
@@ -97,48 +137,107 @@ function v({ date: n, events: a, startHour: o, endHour: f, slotDuration: h, onEv
97
137
  })
98
138
  }, t.id);
99
139
  }),
100
- T != null && /* @__PURE__ */ r("div", {
101
- className: "absolute left-0 right-0 h-ds-01 bg-error-9 z-10 pointer-events-none",
102
- style: { top: `${T}%` },
140
+ P != null && /* @__PURE__ */ r("div", {
141
+ ref: O,
142
+ className: "pointer-events-none absolute inset-x-0 z-raised h-ds-01 bg-error-9",
143
+ style: { top: `${P}%` },
103
144
  "aria-hidden": "true",
104
145
  children: /* @__PURE__ */ r(t, {
105
146
  color: "error",
106
147
  size: "lg",
107
148
  pulse: !0,
108
- className: "absolute -left-[5px] -top-[4px]"
149
+ className: "absolute -translate-x-1/2 -translate-y-1/2"
109
150
  })
110
151
  })
111
152
  ]
112
153
  })]
113
154
  });
114
155
  }
115
- var y = n.forwardRef(({ view: t, date: o, events: c, onEventClick: l, onSlotClick: u, startHour: d = 8, endHour: p = 18, slotDuration: m = 30, className: h, ...g }, y) => {
116
- let b = n.useMemo(() => {
156
+ var y = n.forwardRef(({ view: t, date: o, events: c, onEventClick: l, onSlotClick: d, startHour: p = 8, endHour: m = 18, slotDuration: h = 30, selectedEventId: g, renderEvent: y, header: b, emptyState: x, height: S = 480, className: C, ...w }, T) => {
157
+ let E = n.useMemo(() => {
117
158
  if (t === "day") return [o];
118
159
  let e = f(o, { weekStartsOn: 1 });
119
160
  return Array.from({ length: 7 }, (t, n) => a(e, n));
120
- }, [t, o]);
161
+ }, [t, o]), D = !!d, O = (m - p) * 60 / h, [k, A] = n.useState(() => /* @__PURE__ */ new Date());
162
+ n.useEffect(() => {
163
+ let e = setInterval(() => A(/* @__PURE__ */ new Date()), 6e4);
164
+ return () => clearInterval(e);
165
+ }, []);
166
+ let j = n.useRef(null), M = n.useRef(null), [N, P] = n.useState({
167
+ day: 0,
168
+ slot: 0
169
+ });
170
+ n.useEffect(() => {
171
+ M.current?.scrollIntoView({ block: "center" });
172
+ }, []);
173
+ let F = (e) => {
174
+ if (!D) return;
175
+ let t = e.target, n = t.getAttribute("data-day"), r = t.getAttribute("data-slot");
176
+ if (n == null || r == null) return;
177
+ let i = Number(n), a = Number(r), o = getComputedStyle(j.current ?? t).direction === "rtl", s = i, c = a;
178
+ switch (e.key) {
179
+ case "ArrowDown":
180
+ c = Math.min(a + 1, O - 1);
181
+ break;
182
+ case "ArrowUp":
183
+ c = Math.max(a - 1, 0);
184
+ break;
185
+ case "ArrowRight":
186
+ s = o ? Math.max(i - 1, 0) : Math.min(i + 1, E.length - 1);
187
+ break;
188
+ case "ArrowLeft":
189
+ s = o ? Math.min(i + 1, E.length - 1) : Math.max(i - 1, 0);
190
+ break;
191
+ case "Home":
192
+ c = 0;
193
+ break;
194
+ case "End":
195
+ c = O - 1;
196
+ break;
197
+ default: return;
198
+ }
199
+ e.preventDefault(), P({
200
+ day: s,
201
+ slot: c
202
+ }), j.current?.querySelector(`[data-day="${s}"][data-slot="${c}"]`)?.focus();
203
+ }, I = c.length === 0;
121
204
  return /* @__PURE__ */ i("div", {
122
- ref: y,
123
- role: "region",
124
- "aria-label": t === "day" ? `Schedule for ${s(o, "EEEE, MMMM d, yyyy")}` : `Week schedule starting ${s(b[0], "MMMM d, yyyy")}`,
125
- className: e("flex rounded-control border border-card-strong bg-surface-raised overflow-hidden", "h-[480px]", h),
126
- ...g,
127
- children: [/* @__PURE__ */ r(_, {
128
- startHour: d,
129
- endHour: p
130
- }), /* @__PURE__ */ r("div", {
131
- className: "flex flex-1 divide-x divide-surface-border overflow-x-auto",
132
- children: b.map((e) => /* @__PURE__ */ r(v, {
133
- date: e,
134
- events: c,
135
- startHour: d,
136
- endHour: p,
137
- slotDuration: m,
138
- onEventClick: l,
139
- onSlotClick: u,
140
- showHeader: t === "week"
141
- }, e.toISOString()))
205
+ ref: T,
206
+ className: e("flex flex-col", C),
207
+ ...w,
208
+ children: [b, /* @__PURE__ */ i("div", {
209
+ ref: j,
210
+ role: "region",
211
+ "aria-label": t === "day" ? `Schedule for ${s(o, "EEEE, MMMM d, yyyy")}` : `Week schedule starting ${s(E[0], "MMMM d, yyyy")}`,
212
+ className: "flex overflow-hidden rounded-surface border border-surface-border-strong bg-surface-raised",
213
+ style: { height: typeof S == "number" ? `${S}px` : S },
214
+ children: [/* @__PURE__ */ r(_, {
215
+ startHour: p,
216
+ endHour: m
217
+ }), /* @__PURE__ */ i("div", {
218
+ className: "relative flex flex-1 divide-x divide-surface-border overflow-x-auto overflow-y-auto",
219
+ children: [E.map((e, n) => /* @__PURE__ */ r(v, {
220
+ dayIndex: n,
221
+ date: e,
222
+ events: c,
223
+ startHour: p,
224
+ endHour: m,
225
+ slotDuration: h,
226
+ now: k,
227
+ active: N,
228
+ interactive: D,
229
+ onSlotKeyDown: F,
230
+ selectedEventId: g,
231
+ renderEvent: y,
232
+ onEventClick: l,
233
+ onSlotClick: d,
234
+ showHeader: t === "week",
235
+ nowLineRef: n === 0 || u(e, k) ? M : void 0
236
+ }, e.toISOString())), I && /* @__PURE__ */ r("div", {
237
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center p-ds-05 text-center text-body-sm text-surface-fg-muted",
238
+ children: x ?? "No events scheduled."
239
+ })]
240
+ })]
142
241
  })]
143
242
  });
144
243
  });
@@ -1 +1 @@
1
- {"version":3,"file":"schedule-view.js","names":[],"sources":["../../src/composed/schedule-view.tsx"],"sourcesContent":["'use client'\n\nimport {\n addDays,\n differenceInMinutes,\n format,\n getHours,\n getMinutes,\n isSameDay,\n isToday,\n startOfWeek,\n} from 'date-fns'\nimport * as React from 'react'\n\nimport { Dot } from '../ui/dot'\nimport { cn } from '../ui/lib/utils'\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\n\nexport interface ScheduleEvent {\n id: string\n title: string\n start: Date\n end: Date\n color?: 'accent' | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n}\n\n/**\n * A day/week calendar schedule grid. Renders time-slotted events with\n * proportional height, color coding, click handlers, and a current-time indicator.\n */\nexport interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Display mode: single-day column or full-week (7 columns). */\n view: 'day' | 'week'\n /** Current day (day view) or any date within the target week (week view). */\n date: Date\n /** Events to display on the calendar grid. */\n events: ScheduleEvent[]\n /** Called when an event block is clicked. */\n onEventClick?: (event: ScheduleEvent) => void\n /** Called when an empty time slot is clicked, with the slot's start and end times. */\n onSlotClick?: (start: Date, end: Date) => void\n /** First visible hour (default 8). */\n startHour?: number\n /** Last visible hour -- exclusive (default 18). */\n endHour?: number\n /** Slot duration in minutes (default 30). */\n slotDuration?: number\n}\n\n/* ------------------------------------------------------------------ */\n/* Color map */\n/* ------------------------------------------------------------------ */\n\nconst eventColorMap: Record<\n NonNullable<ScheduleEvent['color']>,\n string\n> = {\n accent: 'bg-accent-2 text-accent-11',\n success: 'bg-success-3 text-success-11',\n warning: 'bg-warning-3 text-warning-11',\n error: 'bg-error-3 text-error-11',\n info: 'bg-info-3 text-info-11',\n neutral: 'bg-surface-raised text-surface-fg-muted',\n}\n\n// Solid category dot — the color-blind-safe, shape-based signal that replaces the\n// former left accent rail (the AI tell). Tint above carries the ambient color.\nconst eventDotMap: Record<\n NonNullable<ScheduleEvent['color']>,\n string\n> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n neutral: 'bg-surface-fg-subtle',\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatHourLabel(hour: number): string {\n if (hour === 0) return '12 AM'\n if (hour < 12) return `${hour} AM`\n if (hour === 12) return '12 PM'\n return `${hour - 12} PM`\n}\n\nfunction getEventStyle(\n event: ScheduleEvent,\n startHour: number,\n endHour: number,\n) {\n const totalMinutes = (endHour - startHour) * 60\n const eventStartMinutes =\n (getHours(event.start) - startHour) * 60 + getMinutes(event.start)\n const durationMinutes = differenceInMinutes(event.end, event.start)\n\n const top = (eventStartMinutes / totalMinutes) * 100\n const height = (durationMinutes / totalMinutes) * 100\n\n return {\n top: `${top}%`,\n height: `${height}%`,\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Sub-components */\n/* ------------------------------------------------------------------ */\n\ninterface TimeColumnProps {\n startHour: number\n endHour: number\n}\n\nfunction TimeColumn({ startHour, endHour }: TimeColumnProps) {\n const hours: number[] = []\n for (let h = startHour; h < endHour; h++) {\n hours.push(h)\n }\n const slotHeight = 100 / (endHour - startHour)\n\n return (\n <div\n className=\"relative shrink-0 w-[60px] border-r border-surface-border-strong\"\n aria-hidden=\"true\"\n >\n {hours.map((hour) => (\n <div\n key={hour}\n className=\"text-caption text-surface-fg-muted pr-ds-02 text-right\"\n style={{ height: `${slotHeight}%` }}\n >\n {formatHourLabel(hour)}\n </div>\n ))}\n </div>\n )\n}\n\ninterface DayColumnProps {\n date: Date\n events: ScheduleEvent[]\n startHour: number\n endHour: number\n slotDuration: number\n onEventClick?: (event: ScheduleEvent) => void\n onSlotClick?: (start: Date, end: Date) => void\n showHeader?: boolean\n}\n\nfunction DayColumn({\n date,\n events,\n startHour,\n endHour,\n slotDuration,\n onEventClick,\n onSlotClick,\n showHeader,\n}: DayColumnProps) {\n const dayEvents = events.filter((e) => isSameDay(e.start, date))\n const totalMinutes = (endHour - startHour) * 60\n const slotCount = totalMinutes / slotDuration\n\n const slots: { start: Date; end: Date }[] = []\n for (let i = 0; i < slotCount; i++) {\n const slotStart = new Date(date)\n slotStart.setHours(startHour, i * slotDuration, 0, 0)\n const slotEnd = new Date(slotStart)\n slotEnd.setMinutes(slotEnd.getMinutes() + slotDuration)\n slots.push({ start: slotStart, end: slotEnd })\n }\n\n const todayInView = isToday(date)\n\n // Current time indicator position\n let nowIndicatorTop: number | null = null\n if (todayInView) {\n const now = new Date()\n const nowHour = getHours(now)\n const nowMin = getMinutes(now)\n if (nowHour >= startHour && nowHour < endHour) {\n const nowMinutes = (nowHour - startHour) * 60 + nowMin\n nowIndicatorTop = (nowMinutes / totalMinutes) * 100\n }\n }\n\n return (\n <div className=\"flex flex-1 flex-col min-w-ds-11\">\n {showHeader && (\n <div\n className={cn(\n 'text-center text-body-sm font-semibold py-ds-02 border-b border-surface-border-strong',\n todayInView\n ? 'text-accent-11 bg-accent-2'\n : 'text-surface-fg bg-surface-raised',\n )}\n >\n {format(date, 'EEE d')}\n </div>\n )}\n <div className=\"relative flex-1\">\n {/* Slot grid lines */}\n {slots.map((slot, i) => (\n <button\n key={i}\n type=\"button\"\n className={cn(\n 'block w-full border-b border-surface-border hover:bg-surface-raised-hover transition-colors ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent-9',\n i % 2 === 0 ? 'border-surface-border-strong' : 'border-surface-border-subtle',\n )}\n style={{ height: `${100 / slotCount}%` }}\n onClick={() => onSlotClick?.(slot.start, slot.end)}\n aria-label={`${format(slot.start, 'h:mm a')} to ${format(slot.end, 'h:mm a')}`}\n />\n ))}\n\n {/* Events */}\n {dayEvents.map((event) => {\n const style = getEventStyle(event, startHour, endHour)\n const eventColor = event.color ?? 'accent'\n const colorClass = eventColorMap[eventColor]\n const dotClass = eventDotMap[eventColor]\n return (\n <button\n key={event.id}\n type=\"button\"\n className={cn(\n 'absolute left-ds-01 right-ds-01 rounded-control-inner px-ds-02 py-ds-01',\n 'text-left text-body-xs font-medium overflow-hidden cursor-pointer',\n 'hover:shadow-raised transition-[box-shadow] duration-fast-02 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9',\n colorClass,\n )}\n style={style}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n aria-label={`${event.title}, ${format(event.start, 'h:mm a')} to ${format(event.end, 'h:mm a')}`}\n >\n <span className=\"flex items-start gap-ds-02\">\n <span\n className={cn('mt-[3px] h-ds-03 w-ds-03 shrink-0 rounded-pill', dotClass)}\n aria-hidden=\"true\"\n />\n <span className=\"line-clamp-2\">{event.title}</span>\n </span>\n </button>\n )\n })}\n\n {/* Current time indicator */}\n {nowIndicatorTop != null && (\n <div\n className=\"absolute left-0 right-0 h-ds-01 bg-error-9 z-10 pointer-events-none\"\n style={{ top: `${nowIndicatorTop}%` }}\n aria-hidden=\"true\"\n >\n <Dot color=\"error\" size=\"lg\" pulse className=\"absolute -left-[5px] -top-[4px]\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* ScheduleView */\n/* ------------------------------------------------------------------ */\n\nconst ScheduleView = React.forwardRef<HTMLDivElement, ScheduleViewProps>(\n (\n {\n view,\n date,\n events,\n onEventClick,\n onSlotClick,\n startHour = 8,\n endHour = 18,\n slotDuration = 30,\n className,\n ...props\n },\n ref,\n ) => {\n const days: Date[] = React.useMemo(() => {\n if (view === 'day') return [date]\n const weekStart = startOfWeek(date, { weekStartsOn: 1 }) // Monday\n return Array.from({ length: 7 }, (_, i) => addDays(weekStart, i))\n }, [view, date])\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={\n view === 'day'\n ? `Schedule for ${format(date, 'EEEE, MMMM d, yyyy')}`\n : `Week schedule starting ${format(days[0], 'MMMM d, yyyy')}`\n }\n className={cn(\n 'flex rounded-control border border-card-strong bg-surface-raised overflow-hidden',\n 'h-[480px]',\n className,\n )}\n {...props}\n >\n <TimeColumn startHour={startHour} endHour={endHour} />\n <div className=\"flex flex-1 divide-x divide-surface-border overflow-x-auto\">\n {days.map((day) => (\n <DayColumn\n key={day.toISOString()}\n date={day}\n events={events}\n startHour={startHour}\n endHour={endHour}\n slotDuration={slotDuration}\n onEventClick={onEventClick}\n onSlotClick={onSlotClick}\n showHeader={view === 'week'}\n />\n ))}\n </div>\n </div>\n )\n },\n)\nScheduleView.displayName = 'ScheduleView'\n\nexport { ScheduleView }\n"],"mappings":"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"}
1
+ {"version":3,"file":"schedule-view.js","names":[],"sources":["../../src/composed/schedule-view.tsx"],"sourcesContent":["'use client'\n\nimport {\n addDays,\n differenceInMinutes,\n format,\n getHours,\n getMinutes,\n isSameDay,\n isToday,\n startOfWeek,\n} from 'date-fns'\nimport * as React from 'react'\n\nimport { Dot } from '../ui/dot'\nimport { cn } from '../ui/lib/utils'\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\n\nexport interface ScheduleEvent {\n id: string\n title: string\n start: Date\n end: Date\n color?: 'accent' | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n}\n\n/**\n * A day/week calendar schedule grid. Renders time-slotted events with\n * proportional height, color coding, overlapping-event columns, a live\n * current-time indicator, and keyboard-navigable slots (when `onSlotClick` is\n * set). Presentational — drive `events`/`date`/`view` from the parent.\n */\nexport interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Display mode: single-day column or full-week (7 columns). */\n view: 'day' | 'week'\n /** Current day (day view) or any date within the target week (week view). */\n date: Date\n /** Events to display on the calendar grid. */\n events: ScheduleEvent[]\n /** Called when an event block is clicked. */\n onEventClick?: (event: ScheduleEvent) => void\n /**\n * Called when an empty time slot is clicked. When omitted, slots render as\n * non-interactive grid lines (no keyboard/AT tab stops).\n */\n onSlotClick?: (start: Date, end: Date) => void\n /** First visible hour (default 8). */\n startHour?: number\n /** Last visible hour -- exclusive (default 18). */\n endHour?: number\n /** Slot duration in minutes (default 30). */\n slotDuration?: number\n /** Currently-selected event id — rendered with a ring. */\n selectedEventId?: string\n /** Custom event content renderer. Receives the event; return the block body. */\n renderEvent?: (event: ScheduleEvent) => React.ReactNode\n /** Optional toolbar/header rendered above the grid. */\n header?: React.ReactNode\n /** Content shown when there are no events in view. */\n emptyState?: React.ReactNode\n /** Grid body height (CSS length or px number). @default 480 */\n height?: number | string\n}\n\n/* ------------------------------------------------------------------ */\n/* Color map */\n/* ------------------------------------------------------------------ */\n\nconst eventColorMap: Record<NonNullable<ScheduleEvent['color']>, string> = {\n accent: 'bg-accent-2 text-accent-11',\n success: 'bg-success-3 text-success-11',\n warning: 'bg-warning-3 text-warning-11',\n error: 'bg-error-3 text-error-11',\n info: 'bg-info-3 text-info-11',\n neutral: 'bg-surface-raised text-surface-fg-muted',\n}\n\nconst eventDotMap: Record<NonNullable<ScheduleEvent['color']>, string> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n neutral: 'bg-surface-fg-subtle',\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatHourLabel(hour: number): string {\n if (hour === 0) return '12 AM'\n if (hour < 12) return `${hour} AM`\n if (hour === 12) return '12 PM'\n return `${hour - 12} PM`\n}\n\ninterface PositionedEvent {\n event: ScheduleEvent\n top: number\n height: number\n col: number\n cols: number\n}\n\n/**\n * Position a day's events, partitioning concurrent events into side-by-side\n * columns so overlaps stay legible (greedy interval colouring per overlap\n * cluster).\n */\nfunction layoutDayEvents(\n events: ScheduleEvent[],\n startHour: number,\n endHour: number,\n): PositionedEvent[] {\n const totalMinutes = (endHour - startHour) * 60\n const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime())\n\n const result: PositionedEvent[] = []\n let cluster: PositionedEvent[] = []\n let clusterEnd = -Infinity\n\n const flush = () => {\n const cols = cluster.reduce((m, e) => Math.max(m, e.col + 1), 0)\n for (const e of cluster) e.cols = cols\n result.push(...cluster)\n cluster = []\n clusterEnd = -Infinity\n }\n\n for (const event of sorted) {\n const startM = (getHours(event.start) - startHour) * 60 + getMinutes(event.start)\n const durationM = Math.max(differenceInMinutes(event.end, event.start), 1)\n const startMs = event.start.getTime()\n\n // New cluster when this event starts after everything so far has ended.\n if (startMs >= clusterEnd && cluster.length > 0) flush()\n\n // First free column within the cluster.\n const taken = new Set(cluster.filter((c) => c.event.end.getTime() > startMs).map((c) => c.col))\n let col = 0\n while (taken.has(col)) col++\n\n cluster.push({\n event,\n top: (startM / totalMinutes) * 100,\n height: (durationM / totalMinutes) * 100,\n col,\n cols: 1,\n })\n clusterEnd = Math.max(clusterEnd, event.end.getTime())\n }\n if (cluster.length > 0) flush()\n\n return result\n}\n\n/* ------------------------------------------------------------------ */\n/* Time column */\n/* ------------------------------------------------------------------ */\n\nfunction TimeColumn({ startHour, endHour }: { startHour: number; endHour: number }) {\n const hours: number[] = []\n for (let h = startHour; h < endHour; h++) hours.push(h)\n const slotHeight = 100 / (endHour - startHour)\n\n return (\n <div className=\"relative w-ds-11 shrink-0 border-e border-surface-border-strong\" aria-hidden=\"true\">\n {hours.map((hour) => (\n <div\n key={hour}\n className=\"pe-ds-02 text-end text-caption text-surface-fg-muted\"\n style={{ height: `${slotHeight}%` }}\n >\n {formatHourLabel(hour)}\n </div>\n ))}\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* Day column */\n/* ------------------------------------------------------------------ */\n\ninterface DayColumnProps {\n dayIndex: number\n date: Date\n events: ScheduleEvent[]\n startHour: number\n endHour: number\n slotDuration: number\n now: Date\n active: { day: number; slot: number }\n interactive: boolean\n onSlotKeyDown?: (e: React.KeyboardEvent<HTMLButtonElement>) => void\n selectedEventId?: string\n renderEvent?: (event: ScheduleEvent) => React.ReactNode\n onEventClick?: (event: ScheduleEvent) => void\n onSlotClick?: (start: Date, end: Date) => void\n showHeader?: boolean\n nowLineRef?: React.Ref<HTMLDivElement>\n}\n\nfunction DayColumn({\n dayIndex,\n date,\n events,\n startHour,\n endHour,\n slotDuration,\n now,\n active,\n interactive,\n onSlotKeyDown,\n selectedEventId,\n renderEvent,\n onEventClick,\n onSlotClick,\n showHeader,\n nowLineRef,\n}: DayColumnProps) {\n const dayEvents = React.useMemo(\n () => layoutDayEvents(events.filter((e) => isSameDay(e.start, date)), startHour, endHour),\n [events, date, startHour, endHour],\n )\n const totalMinutes = (endHour - startHour) * 60\n const slotCount = totalMinutes / slotDuration\n\n const slots = React.useMemo(() => {\n const out: { start: Date; end: Date }[] = []\n for (let i = 0; i < slotCount; i++) {\n const slotStart = new Date(date)\n slotStart.setHours(startHour, i * slotDuration, 0, 0)\n const slotEnd = new Date(slotStart)\n slotEnd.setMinutes(slotEnd.getMinutes() + slotDuration)\n out.push({ start: slotStart, end: slotEnd })\n }\n return out\n }, [date, startHour, slotDuration, slotCount])\n\n const todayInView = isToday(date)\n let nowIndicatorTop: number | null = null\n if (todayInView) {\n const nowHour = getHours(now)\n if (nowHour >= startHour && nowHour < endHour) {\n const nowMinutes = (nowHour - startHour) * 60 + getMinutes(now)\n nowIndicatorTop = (nowMinutes / totalMinutes) * 100\n }\n }\n\n return (\n <div className=\"flex min-w-ds-11 flex-1 flex-col\">\n {showHeader && (\n <div\n className={cn(\n 'border-b border-surface-border-strong py-ds-02 text-center text-body-sm font-semibold',\n todayInView ? 'bg-accent-2 text-accent-11' : 'bg-surface-raised text-surface-fg',\n )}\n >\n {format(date, 'EEE d')}\n </div>\n )}\n <div className=\"relative flex-1\">\n {/* Slot lines — interactive buttons only when onSlotClick is set,\n otherwise inert grid lines (no tab stops, hidden from AT). */}\n {slots.map((slot, i) =>\n interactive ? (\n <button\n key={i}\n type=\"button\"\n data-day={dayIndex}\n data-slot={i}\n tabIndex={dayIndex === active.day && i === active.slot ? 0 : -1}\n className={cn(\n 'block w-full min-h-ds-06 border-b transition-colors ease-productive-standard duration-fast-02',\n 'hover:bg-surface-raised-hover',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent-9',\n i % 2 === 0 ? 'border-surface-border-strong' : 'border-surface-border-subtle',\n )}\n style={{ height: `${100 / slotCount}%` }}\n onClick={() => onSlotClick?.(slot.start, slot.end)}\n onKeyDown={onSlotKeyDown}\n aria-label={`${format(slot.start, 'EEE h:mm a')} to ${format(slot.end, 'h:mm a')}`}\n />\n ) : (\n <div\n key={i}\n aria-hidden=\"true\"\n className={cn(\n 'w-full border-b',\n i % 2 === 0 ? 'border-surface-border-strong' : 'border-surface-border-subtle',\n )}\n style={{ height: `${100 / slotCount}%` }}\n />\n ),\n )}\n\n {/* Events — column-partitioned so overlaps sit side by side */}\n {dayEvents.map(({ event, top, height, col, cols }) => {\n const eventColor = event.color ?? 'accent'\n const colWidth = 100 / cols\n const selected = event.id === selectedEventId\n return (\n <button\n key={event.id}\n type=\"button\"\n aria-pressed={selected || undefined}\n className={cn(\n 'absolute overflow-hidden rounded-control-inner px-ds-02 py-ds-01',\n 'cursor-pointer text-start text-body-xs font-medium',\n 'transition-[box-shadow] ease-productive-standard duration-fast-02 hover:shadow-raised',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9',\n 'motion-safe:active:scale-[0.98]',\n selected && 'ring-2 ring-accent-9',\n eventColorMap[eventColor],\n )}\n style={{\n top: `${top}%`,\n height: `${height}%`,\n insetInlineStart: `calc(${col * colWidth}% + 2px)`,\n width: `calc(${colWidth}% - 4px)`,\n }}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n aria-label={`${event.title}, ${format(event.start, 'h:mm a')} to ${format(event.end, 'h:mm a')}`}\n >\n {renderEvent ? (\n renderEvent(event)\n ) : (\n <span className=\"flex items-start gap-ds-02\">\n <span\n className={cn('mt-0.5 h-ds-03 w-ds-03 shrink-0 rounded-pill', eventDotMap[eventColor])}\n aria-hidden=\"true\"\n />\n <span className=\"line-clamp-2\">{event.title}</span>\n </span>\n )}\n </button>\n )\n })}\n\n {/* Live current-time indicator */}\n {nowIndicatorTop != null && (\n <div\n ref={nowLineRef}\n className=\"pointer-events-none absolute inset-x-0 z-raised h-ds-01 bg-error-9\"\n style={{ top: `${nowIndicatorTop}%` }}\n aria-hidden=\"true\"\n >\n <Dot color=\"error\" size=\"lg\" pulse className=\"absolute -translate-x-1/2 -translate-y-1/2\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* ScheduleView */\n/* ------------------------------------------------------------------ */\n\nconst ScheduleView = React.forwardRef<HTMLDivElement, ScheduleViewProps>(\n (\n {\n view,\n date,\n events,\n onEventClick,\n onSlotClick,\n startHour = 8,\n endHour = 18,\n slotDuration = 30,\n selectedEventId,\n renderEvent,\n header,\n emptyState,\n height = 480,\n className,\n ...props\n },\n ref,\n ) => {\n const days = React.useMemo<Date[]>(() => {\n if (view === 'day') return [date]\n const weekStart = startOfWeek(date, { weekStartsOn: 1 })\n return Array.from({ length: 7 }, (_, i) => addDays(weekStart, i))\n }, [view, date])\n\n const interactive = !!onSlotClick\n const slotCount = ((endHour - startHour) * 60) / slotDuration\n\n // Live now-line: re-render each minute so the indicator actually ticks.\n const [now, setNow] = React.useState(() => new Date())\n React.useEffect(() => {\n const id = setInterval(() => setNow(new Date()), 60_000)\n return () => clearInterval(id)\n }, [])\n\n // Roving-tabindex slot navigation (only meaningful when interactive).\n const gridRef = React.useRef<HTMLDivElement>(null)\n const nowLineRef = React.useRef<HTMLDivElement>(null)\n const [active, setActive] = React.useState<{ day: number; slot: number }>({ day: 0, slot: 0 })\n\n // Scroll the now-line into view on mount.\n React.useEffect(() => {\n nowLineRef.current?.scrollIntoView({ block: 'center' })\n }, [])\n\n const handleSlotKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if (!interactive) return\n const target = e.target as HTMLElement\n const dayAttr = target.getAttribute('data-day')\n const slotAttr = target.getAttribute('data-slot')\n if (dayAttr == null || slotAttr == null) return\n const day = Number(dayAttr)\n const slot = Number(slotAttr)\n const rtl = getComputedStyle(gridRef.current ?? target).direction === 'rtl'\n let nextDay = day\n let nextSlot = slot\n switch (e.key) {\n case 'ArrowDown': nextSlot = Math.min(slot + 1, slotCount - 1); break\n case 'ArrowUp': nextSlot = Math.max(slot - 1, 0); break\n case 'ArrowRight': nextDay = rtl ? Math.max(day - 1, 0) : Math.min(day + 1, days.length - 1); break\n case 'ArrowLeft': nextDay = rtl ? Math.min(day + 1, days.length - 1) : Math.max(day - 1, 0); break\n case 'Home': nextSlot = 0; break\n case 'End': nextSlot = slotCount - 1; break\n default: return\n }\n e.preventDefault()\n setActive({ day: nextDay, slot: nextSlot })\n // Focus the target cell now — focus() works regardless of the tabIndex\n // the re-render is about to move onto it.\n gridRef.current\n ?.querySelector<HTMLButtonElement>(`[data-day=\"${nextDay}\"][data-slot=\"${nextSlot}\"]`)\n ?.focus()\n }\n\n const isEmpty = events.length === 0\n\n return (\n <div ref={ref} className={cn('flex flex-col', className)} {...props}>\n {header}\n <div\n ref={gridRef}\n role=\"region\"\n aria-label={\n view === 'day'\n ? `Schedule for ${format(date, 'EEEE, MMMM d, yyyy')}`\n : `Week schedule starting ${format(days[0], 'MMMM d, yyyy')}`\n }\n className=\"flex overflow-hidden rounded-surface border border-surface-border-strong bg-surface-raised\"\n style={{ height: typeof height === 'number' ? `${height}px` : height }}\n >\n <TimeColumn startHour={startHour} endHour={endHour} />\n <div className=\"relative flex flex-1 divide-x divide-surface-border overflow-x-auto overflow-y-auto\">\n {days.map((day, i) => (\n <DayColumn\n key={day.toISOString()}\n dayIndex={i}\n date={day}\n events={events}\n startHour={startHour}\n endHour={endHour}\n slotDuration={slotDuration}\n now={now}\n active={active}\n interactive={interactive}\n onSlotKeyDown={handleSlotKeyDown}\n selectedEventId={selectedEventId}\n renderEvent={renderEvent}\n onEventClick={onEventClick}\n onSlotClick={onSlotClick}\n showHeader={view === 'week'}\n nowLineRef={i === 0 || isSameDay(day, now) ? nowLineRef : undefined}\n />\n ))}\n {isEmpty && (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center p-ds-05 text-center text-body-sm text-surface-fg-muted\">\n {emptyState ?? 'No events scheduled.'}\n </div>\n )}\n </div>\n </div>\n </div>\n )\n },\n)\nScheduleView.displayName = 'ScheduleView'\n\nexport { ScheduleView }\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"motion-provider.d.ts","sourceRoot":"","sources":["../../src/motion/motion-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAElD,KAAK,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEzC,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,OAAO,OAAO,CAAA;IACvB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AAQD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC,CAAA;AAED,iBAAS,cAAc,CAAC,EAAE,QAAQ,EAAE,aAAsB,EAAE,EAAE,mBAAmB,qBAgBhF;AAED,iBAAS,SAAS,uBAEjB;AAED,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAC,SAAS,EAAE,CAAA"}
1
+ {"version":3,"file":"motion-provider.d.ts","sourceRoot":"","sources":["../../src/motion/motion-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAElD,KAAK,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEzC,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,OAAO,OAAO,CAAA;IACvB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AAOD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC,CAAA;AAED,iBAAS,cAAc,CAAC,EAAE,QAAQ,EAAE,aAAsB,EAAE,EAAE,mBAAmB,qBAgBhF;AAED,iBAAS,SAAS,IAAI,kBAAkB,CAMvC;AAED,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAC,SAAS,EAAE,CAAA"}