@crisp-ui-kit/crisp 0.8.0 → 0.9.1

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 (37) hide show
  1. package/dist/appshell.css +2 -0
  2. package/dist/breadcrumb.css +2 -0
  3. package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
  4. package/dist/cjs/components/breadcrumb/breadcrumb.recipe.js +3 -1
  5. package/dist/cjs/components/timepicker/index.js +17 -0
  6. package/dist/cjs/components/timepicker/timepicker.js +54 -0
  7. package/dist/cjs/core/index.js +15 -0
  8. package/dist/cjs/core/rtl.js +17 -0
  9. package/dist/cjs/index.js +1 -0
  10. package/dist/cjs/product/appshell/appshell.js +1 -1
  11. package/dist/cjs/product/appshell/appshell.recipe.js +2 -0
  12. package/dist/cjs/product/recordstable/recordstable.js +2 -2
  13. package/dist/cjs/product/recordstable/recordstable.recipe.js +2 -0
  14. package/dist/cjs/product/settingsshell/settingsshell.js +1 -1
  15. package/dist/cjs/product/settingsshell/settingsshell.recipe.js +3 -1
  16. package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
  17. package/dist/esm/components/breadcrumb/breadcrumb.recipe.js +4 -2
  18. package/dist/esm/components/timepicker/index.js +1 -0
  19. package/dist/esm/components/timepicker/timepicker.js +51 -0
  20. package/dist/esm/core/index.js +1 -0
  21. package/dist/esm/core/rtl.js +14 -0
  22. package/dist/esm/index.js +1 -0
  23. package/dist/esm/product/appshell/appshell.js +1 -1
  24. package/dist/esm/product/appshell/appshell.recipe.js +3 -1
  25. package/dist/esm/product/recordstable/recordstable.js +2 -2
  26. package/dist/esm/product/recordstable/recordstable.recipe.js +3 -1
  27. package/dist/esm/product/settingsshell/settingsshell.js +1 -1
  28. package/dist/esm/product/settingsshell/settingsshell.recipe.js +4 -2
  29. package/dist/recordstable.css +2 -0
  30. package/dist/settingsshell.css +2 -0
  31. package/dist/styles.css +8 -0
  32. package/dist/types/components/timepicker/index.d.ts +1 -0
  33. package/dist/types/components/timepicker/timepicker.d.ts +15 -0
  34. package/dist/types/core/index.d.ts +1 -0
  35. package/dist/types/core/rtl.d.ts +14 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/package.json +7 -1
package/dist/appshell.css CHANGED
@@ -10,6 +10,8 @@
10
10
  & .appshell-expand { position: absolute; top: var(--space-2); inset-inline-start: var(--space-2); z-index: 6; display: inline-flex; align-items: center; justify-content: center; width: var(--size-row); height: var(--size-row); padding: 0; border: none; border-radius: var(--radius-md); box-shadow: inset 0 0 0 1px var(--border-subtle); background: var(--bg-surface); color: var(--fg-muted); font-family: inherit; cursor: pointer; transition: background-color .12s; }
11
11
  & .appshell-expand:hover { background: var(--bg-raised); color: var(--fg-primary); }
12
12
 
13
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
14
+
13
15
  }
14
16
 
15
17
  @media (prefers-reduced-motion: reduce) {
@@ -6,4 +6,6 @@
6
6
  & button.breadcrumb-item { border: none; background: none; font: inherit; cursor: pointer; }
7
7
  & .breadcrumb-item[aria-current] { color: var(--fg-primary); font-weight: var(--weight-medium); }
8
8
  & .breadcrumb-sep { flex-shrink: 0; color: color-mix(in srgb, var(--fg-primary) 28%, transparent); }
9
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
10
+
9
11
  }
@@ -43,7 +43,7 @@ const breadcrumb_recipe_1 = require("./breadcrumb.recipe");
43
43
  * marked `aria-current="page"`; earlier items render as links or buttons.
44
44
  */
45
45
  function Breadcrumb({ items, separator, "aria-label": ariaLabel = "Breadcrumb", className, ...rest }) {
46
- const sep = separator ?? ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }));
46
+ const sep = separator ?? ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { "data-rtl-flip": "", size: 14, strokeWidth: 1.75, "aria-hidden": "true" }));
47
47
  return ((0, jsx_runtime_1.jsx)("nav", { "aria-label": ariaLabel, className: (0, breadcrumb_recipe_1.breadcrumbRecipe)({ className }), ...rest, children: items.map((item, i) => {
48
48
  const last = i === items.length - 1;
49
49
  const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [item.icon, item.label] }));
@@ -12,7 +12,9 @@ const config = (0, core_1.defineRecipe)({
12
12
  & a.breadcrumb-item:hover, & button.breadcrumb-item:hover { color: ${tokens_1.contract.fgPrimary}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 5%, transparent); }
13
13
  & button.breadcrumb-item { border: none; background: none; font: inherit; cursor: pointer; }
14
14
  & .breadcrumb-item[aria-current] { color: ${tokens_1.contract.fgPrimary}; font-weight: ${tokens_1.contract.weight.medium}; }
15
- & .breadcrumb-sep { flex-shrink: 0; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 28%, transparent); }`,
15
+ & .breadcrumb-sep { flex-shrink: 0; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 28%, transparent); }
16
+ ${core_1.rtlFlipCss}
17
+ `,
16
18
  variants: {},
17
19
  defaultVariants: {},
18
20
  compoundVariants: [],
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./timepicker"), exports);
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TimePicker = TimePicker;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_time_picker_1 = require("@seed-design/react-time-picker");
7
+ const wheelpicker_1 = require("../wheelpicker");
8
+ const defaultLabels = {
9
+ period: "AM/PM",
10
+ hour: "Hour",
11
+ minute: "Minute",
12
+ };
13
+ /**
14
+ * An hour/minute wheel, driven by seed-design's headless engine so the column
15
+ * order, the 12- vs 24-hour split and the minute stepping are locale-correct by
16
+ * construction, and rendered with crisp's own `WheelPicker` so the pixels are
17
+ * the ones already measured for it.
18
+ */
19
+ function TimePicker({ value, defaultValue, onValueChange, minuteStep, locale, disabled, labels, size = "medium", className, ...rest }) {
20
+ // The engine defaults `locale` to "ko-KR" — it is a Korean design system.
21
+ // crisp's other components are English-default, so the default is overridden
22
+ // here rather than inherited; a consumer's own `locale` still wins.
23
+ const api = (0, react_time_picker_1.useTimePicker)({
24
+ value,
25
+ defaultValue,
26
+ onValueChange,
27
+ minuteStep,
28
+ locale: locale ?? "en-US",
29
+ disabled,
30
+ });
31
+ const t = { ...defaultLabels, ...labels };
32
+ // `columnOrder` is locale-derived: en-US puts the period last ("3:30 PM"),
33
+ // ko-KR first, and a 24-hour locale omits it entirely.
34
+ const columns = api.columnOrder.map((type) => ({
35
+ key: type,
36
+ label: t[type],
37
+ options: api.columns[type].options.map((o) => ({
38
+ value: o.value,
39
+ label: o.label,
40
+ })),
41
+ }));
42
+ const selection = Object.fromEntries(api.columnOrder.map((type) => [type, api.columns[type].value]));
43
+ return ((0, jsx_runtime_1.jsx)("div", { ...rest, className: className, children: (0, jsx_runtime_1.jsx)(wheelpicker_1.WheelPicker, { columns: columns, value: selection, size: size, onValueChange: (next) => {
44
+ // Route each wheel back to its OWN column, so the engine keeps
45
+ // ownership of rounding, looping and the 12-to-24-hour mapping.
46
+ // Sending them all to one column silently makes the period wheel
47
+ // set the hour.
48
+ for (const type of api.columnOrder) {
49
+ const v = next[type];
50
+ if (v != null)
51
+ api.columns[type].onValueChange(v);
52
+ }
53
+ } }) }));
54
+ }
@@ -1,4 +1,18 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.resolveRecipe = exports.Primitive = exports.defineRecipe = exports.cx = void 0;
4
18
  var cx_1 = require("./cx");
@@ -9,3 +23,4 @@ var primitive_1 = require("./primitive");
9
23
  Object.defineProperty(exports, "Primitive", { enumerable: true, get: function () { return primitive_1.Primitive; } });
10
24
  var resolve_1 = require("./resolve");
11
25
  Object.defineProperty(exports, "resolveRecipe", { enumerable: true, get: function () { return resolve_1.resolveRecipe; } });
26
+ __exportStar(require("./rtl"), exports);
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rtlFlipCss = void 0;
4
+ /**
5
+ * Mirroring for direction-bearing icons.
6
+ *
7
+ * A chevron or arrow that means "back", "next", "toward the start" or "open"
8
+ * points the wrong way under RTL, and unlike spacing or alignment it cannot be
9
+ * fixed with a logical property: these are `lucide-react` nodes, static SVG
10
+ * paths with no direction of their own.
11
+ *
12
+ * Mark the icons that carry a direction with `data-rtl-flip` and include this
13
+ * snippet in the recipe. Icons whose meaning is NOT directional — a downward
14
+ * disclosure chevron, a search glass, a check — must be left unmarked: flipping
15
+ * them is a regression, not a fix.
16
+ */
17
+ exports.rtlFlipCss = `[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }`;
package/dist/cjs/index.js CHANGED
@@ -61,6 +61,7 @@ __exportStar(require("./components/table"), exports);
61
61
  __exportStar(require("./components/tabletoolbar"), exports);
62
62
  __exportStar(require("./components/tabs"), exports);
63
63
  __exportStar(require("./components/textarea"), exports);
64
+ __exportStar(require("./components/timepicker"), exports);
64
65
  __exportStar(require("./components/toast"), exports);
65
66
  __exportStar(require("./components/tooltip"), exports);
66
67
  __exportStar(require("./components/wheelpicker"), exports);
@@ -99,6 +99,6 @@ function AppShell({ sidebar, children, width: widthProp, defaultWidth = 275, onW
99
99
  return ((0, jsx_runtime_1.jsxs)("div", { className: (0, appshell_recipe_1.appshellRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "appshell-side", "data-collapsed": collapsed ? "" : undefined, style: {
100
100
  width: collapsed ? 0 : width,
101
101
  transition: dragging ? "none" : "width .14s ease",
102
- }, children: [(0, jsx_runtime_1.jsx)("div", { className: "appshell-side-inner", style: { width }, children: sidebar(toggleCollapse) }), !collapsed && ((0, jsx_runtime_1.jsx)("div", { className: "appshell-resize", "data-dragging": dragging ? "" : undefined, "aria-hidden": "true", onPointerDown: startResize }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "appshell-main", children: [collapsed && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "appshell-expand", "aria-label": expandLabel, onClick: () => setCollapsed(false), children: (0, jsx_runtime_1.jsx)(lucide_react_1.PanelLeft, { size: 15, strokeWidth: 1.75 }) })), children] })] }));
102
+ }, children: [(0, jsx_runtime_1.jsx)("div", { className: "appshell-side-inner", style: { width }, children: sidebar(toggleCollapse) }), !collapsed && ((0, jsx_runtime_1.jsx)("div", { className: "appshell-resize", "data-dragging": dragging ? "" : undefined, "aria-hidden": "true", onPointerDown: startResize }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "appshell-main", children: [collapsed && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "appshell-expand", "aria-label": expandLabel, onClick: () => setCollapsed(false), children: (0, jsx_runtime_1.jsx)(lucide_react_1.PanelLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }) })), children] })] }));
103
103
  }
104
104
  AppShell.displayName = "AppShell";
@@ -18,6 +18,8 @@ const config = (0, core_1.defineRecipe)({
18
18
  & .appshell-main { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
19
19
  & .appshell-expand { position: absolute; top: ${tokens_1.contract.space["2"]}; inset-inline-start: ${tokens_1.contract.space["2"]}; z-index: 6; display: inline-flex; align-items: center; justify-content: center; width: ${tokens_1.contract.size.row}; height: ${tokens_1.contract.size.row}; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderSubtle}; background: ${tokens_1.contract.bgSurface}; color: ${tokens_1.contract.fgMuted}; font-family: inherit; cursor: pointer; transition: background-color .12s; }
20
20
  & .appshell-expand:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
21
+
22
+ ${core_1.rtlFlipCss}
21
23
  `,
22
24
  variants: {},
23
25
  defaultVariants: {},
@@ -984,7 +984,7 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
984
984
  }
985
985
  : undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 14, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)("span", { className: "recordstable-th-label", children: col.label }), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-sort", children: (0, jsx_runtime_1.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "m4 6 4 4 4-4" }) }) })] })), openMenu === `col:${col.key}` &&
986
986
  rootRef.current &&
987
- (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowLeft, { size: 15, strokeWidth: 1.75 }), t.moveLeft] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { size: 15, strokeWidth: 1.75 }), t.moveRight] }), (0, jsx_runtime_1.jsx)("div", { className: "recordstable-menu-sep" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
987
+ (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveLeft] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveRight] }), (0, jsx_runtime_1.jsx)("div", { className: "recordstable-menu-sep" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
988
988
  setRenamingCol(col.key);
989
989
  setOpenMenu(null);
990
990
  }, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }), t.editColumnLabel] }), cols.length > 1 && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => hideColumn(col.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.EyeOff, { size: 15, strokeWidth: 1.75 }), t.hideFromView] }))] }), rootRef.current), (0, jsx_runtime_1.jsx)("span", { className: "recordstable-resize", "aria-hidden": "true", onPointerDown: (e) => {
@@ -1025,7 +1025,7 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
1025
1025
  : undefined, avatarTone: avatarTone })), i === 0 && onOpenRecord && !isEditing && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "recordstable-open", "aria-label": `Open ${cellText(col, row)}`, onClick: (e) => {
1026
1026
  e.stopPropagation();
1027
1027
  onOpenRecord(row);
1028
- }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpRight, { size: 15, strokeWidth: 1.75 }) }))] }, col.key));
1028
+ }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpRight, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }) }))] }, col.key));
1029
1029
  }), (0, jsx_runtime_1.jsx)("td", { className: "recordstable-td recordstable-addcol" })] }, row.id))) }), (0, jsx_runtime_1.jsx)("tfoot", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { className: "recordstable-sum recordstable-col-check" }), cols.map((col, i) => {
1030
1030
  const a = agg[col.key] ?? "none";
1031
1031
  const res = summarize(col, a, visibleRows);
@@ -145,6 +145,8 @@ ${(0, tabletoolbar_chrome_1.toolbarChromeBody)("recordstable")}
145
145
  & .recordstable-bulk-sep { width: 1px; height: 20px; margin: 0 ${tokens_1.contract.space["0.5"]}; background: ${tokens_1.contract.borderSubtle}; }
146
146
  & .recordstable-bulk-close { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: transparent; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
147
147
  & .recordstable-bulk-close:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
148
+
149
+ ${core_1.rtlFlipCss}
148
150
  `,
149
151
  variants: {},
150
152
  defaultVariants: {},
@@ -54,6 +54,6 @@ function SettingsShell({ title = "Settings", onBack, groups, activeItem, onSelec
54
54
  }))
55
55
  .filter((g) => g.items.length > 0)
56
56
  : groups;
57
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, settingsshell_recipe_1.settingsshellRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("nav", { className: "settingsshell-nav", "aria-label": navLabel, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "settingsshell-back", onClick: onBack, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { size: 16, strokeWidth: 2 }), title] }), searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "settingsshell-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value) })] })), (0, jsx_runtime_1.jsx)("div", { className: "settingsshell-groups", children: filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "settingsshell-empty", children: emptyLabel })) : (filtered.map((g, gi) => ((0, jsx_runtime_1.jsxs)("div", { className: "settingsshell-group", children: [g.label != null && ((0, jsx_runtime_1.jsx)("div", { className: "settingsshell-group-label", children: g.label })), g.items.map((it) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "settingsshell-item", "aria-current": it.id === activeItem ? "page" : undefined, "data-active": it.id === activeItem ? "" : undefined, onClick: () => onSelect(it.id), children: [it.icon, (0, jsx_runtime_1.jsx)("span", { className: "settingsshell-item-label", children: it.label })] }, it.id)))] }, gi)))) })] }), (0, jsx_runtime_1.jsx)("div", { className: "settingsshell-content", children: children })] }));
57
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, settingsshell_recipe_1.settingsshellRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("nav", { className: "settingsshell-nav", "aria-label": navLabel, children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "settingsshell-back", onClick: onBack, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 2 }), title] }), searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "settingsshell-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value) })] })), (0, jsx_runtime_1.jsx)("div", { className: "settingsshell-groups", children: filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "settingsshell-empty", children: emptyLabel })) : (filtered.map((g, gi) => ((0, jsx_runtime_1.jsxs)("div", { className: "settingsshell-group", children: [g.label != null && ((0, jsx_runtime_1.jsx)("div", { className: "settingsshell-group-label", children: g.label })), g.items.map((it) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "settingsshell-item", "aria-current": it.id === activeItem ? "page" : undefined, "data-active": it.id === activeItem ? "" : undefined, onClick: () => onSelect(it.id), children: [it.icon, (0, jsx_runtime_1.jsx)("span", { className: "settingsshell-item-label", children: it.label })] }, it.id)))] }, gi)))) })] }), (0, jsx_runtime_1.jsx)("div", { className: "settingsshell-content", children: children })] }));
58
58
  }
59
59
  SettingsShell.displayName = "SettingsShell";
@@ -37,7 +37,9 @@ const config = (0, core_1.defineRecipe)({
37
37
  & .settingsshell-item[data-active] svg { color: ${tokens_1.contract.fgPrimary}; }
38
38
  & .settingsshell-empty { padding: ${tokens_1.contract.space["3"]} ${tokens_1.contract.space["2"]}; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }
39
39
 
40
- & .settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: ${tokens_1.contract.space["6"]} ${tokens_1.contract.space["8"]}; }`,
40
+ & .settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: ${tokens_1.contract.space["6"]} ${tokens_1.contract.space["8"]}; }
41
+ ${core_1.rtlFlipCss}
42
+ `,
41
43
  variants: {},
42
44
  defaultVariants: {},
43
45
  compoundVariants: [],
@@ -7,7 +7,7 @@ import { breadcrumbRecipe } from "./breadcrumb.recipe.js";
7
7
  * marked `aria-current="page"`; earlier items render as links or buttons.
8
8
  */
9
9
  export function Breadcrumb({ items, separator, "aria-label": ariaLabel = "Breadcrumb", className, ...rest }) {
10
- const sep = separator ?? (_jsx(ChevronRight, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }));
10
+ const sep = separator ?? (_jsx(ChevronRight, { "data-rtl-flip": "", size: 14, strokeWidth: 1.75, "aria-hidden": "true" }));
11
11
  return (_jsx("nav", { "aria-label": ariaLabel, className: breadcrumbRecipe({ className }), ...rest, children: items.map((item, i) => {
12
12
  const last = i === items.length - 1;
13
13
  const content = (_jsxs(_Fragment, { children: [item.icon, item.label] }));
@@ -1,4 +1,4 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c } from "../../tokens/index.js";
3
3
  // A breadcrumb trail — Attio's record-page nav ("Companies / Google").
4
4
  const config = defineRecipe({
@@ -9,7 +9,9 @@ const config = defineRecipe({
9
9
  & a.breadcrumb-item:hover, & button.breadcrumb-item:hover { color: ${c.fgPrimary}; background: color-mix(in srgb, ${c.fgPrimary} 5%, transparent); }
10
10
  & button.breadcrumb-item { border: none; background: none; font: inherit; cursor: pointer; }
11
11
  & .breadcrumb-item[aria-current] { color: ${c.fgPrimary}; font-weight: ${c.weight.medium}; }
12
- & .breadcrumb-sep { flex-shrink: 0; color: color-mix(in srgb, ${c.fgPrimary} 28%, transparent); }`,
12
+ & .breadcrumb-sep { flex-shrink: 0; color: color-mix(in srgb, ${c.fgPrimary} 28%, transparent); }
13
+ ${rtlFlipCss}
14
+ `,
13
15
  variants: {},
14
16
  defaultVariants: {},
15
17
  compoundVariants: [],
@@ -0,0 +1 @@
1
+ export * from "./timepicker.js";
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useTimePicker, } from "@seed-design/react-time-picker";
4
+ import { WheelPicker } from "../wheelpicker/index.js";
5
+ const defaultLabels = {
6
+ period: "AM/PM",
7
+ hour: "Hour",
8
+ minute: "Minute",
9
+ };
10
+ /**
11
+ * An hour/minute wheel, driven by seed-design's headless engine so the column
12
+ * order, the 12- vs 24-hour split and the minute stepping are locale-correct by
13
+ * construction, and rendered with crisp's own `WheelPicker` so the pixels are
14
+ * the ones already measured for it.
15
+ */
16
+ export function TimePicker({ value, defaultValue, onValueChange, minuteStep, locale, disabled, labels, size = "medium", className, ...rest }) {
17
+ // The engine defaults `locale` to "ko-KR" — it is a Korean design system.
18
+ // crisp's other components are English-default, so the default is overridden
19
+ // here rather than inherited; a consumer's own `locale` still wins.
20
+ const api = useTimePicker({
21
+ value,
22
+ defaultValue,
23
+ onValueChange,
24
+ minuteStep,
25
+ locale: locale ?? "en-US",
26
+ disabled,
27
+ });
28
+ const t = { ...defaultLabels, ...labels };
29
+ // `columnOrder` is locale-derived: en-US puts the period last ("3:30 PM"),
30
+ // ko-KR first, and a 24-hour locale omits it entirely.
31
+ const columns = api.columnOrder.map((type) => ({
32
+ key: type,
33
+ label: t[type],
34
+ options: api.columns[type].options.map((o) => ({
35
+ value: o.value,
36
+ label: o.label,
37
+ })),
38
+ }));
39
+ const selection = Object.fromEntries(api.columnOrder.map((type) => [type, api.columns[type].value]));
40
+ return (_jsx("div", { ...rest, className: className, children: _jsx(WheelPicker, { columns: columns, value: selection, size: size, onValueChange: (next) => {
41
+ // Route each wheel back to its OWN column, so the engine keeps
42
+ // ownership of rounding, looping and the 12-to-24-hour mapping.
43
+ // Sending them all to one column silently makes the period wheel
44
+ // set the hour.
45
+ for (const type of api.columnOrder) {
46
+ const v = next[type];
47
+ if (v != null)
48
+ api.columns[type].onValueChange(v);
49
+ }
50
+ } }) }));
51
+ }
@@ -2,3 +2,4 @@ export { cx } from "./cx.js";
2
2
  export { defineRecipe } from "./define.js";
3
3
  export { Primitive } from "./primitive.js";
4
4
  export { resolveRecipe } from "./resolve.js";
5
+ export * from "./rtl.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mirroring for direction-bearing icons.
3
+ *
4
+ * A chevron or arrow that means "back", "next", "toward the start" or "open"
5
+ * points the wrong way under RTL, and unlike spacing or alignment it cannot be
6
+ * fixed with a logical property: these are `lucide-react` nodes, static SVG
7
+ * paths with no direction of their own.
8
+ *
9
+ * Mark the icons that carry a direction with `data-rtl-flip` and include this
10
+ * snippet in the recipe. Icons whose meaning is NOT directional — a downward
11
+ * disclosure chevron, a search glass, a check — must be left unmarked: flipping
12
+ * them is a regression, not a fix.
13
+ */
14
+ export const rtlFlipCss = `[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }`;
package/dist/esm/index.js CHANGED
@@ -45,6 +45,7 @@ export * from "./components/table/index.js";
45
45
  export * from "./components/tabletoolbar/index.js";
46
46
  export * from "./components/tabs/index.js";
47
47
  export * from "./components/textarea/index.js";
48
+ export * from "./components/timepicker/index.js";
48
49
  export * from "./components/toast/index.js";
49
50
  export * from "./components/tooltip/index.js";
50
51
  export * from "./components/wheelpicker/index.js";
@@ -63,6 +63,6 @@ export function AppShell({ sidebar, children, width: widthProp, defaultWidth = 2
63
63
  return (_jsxs("div", { className: appshellRecipe({ className }), ...rest, children: [_jsxs("div", { className: "appshell-side", "data-collapsed": collapsed ? "" : undefined, style: {
64
64
  width: collapsed ? 0 : width,
65
65
  transition: dragging ? "none" : "width .14s ease",
66
- }, children: [_jsx("div", { className: "appshell-side-inner", style: { width }, children: sidebar(toggleCollapse) }), !collapsed && (_jsx("div", { className: "appshell-resize", "data-dragging": dragging ? "" : undefined, "aria-hidden": "true", onPointerDown: startResize }))] }), _jsxs("div", { className: "appshell-main", children: [collapsed && (_jsx("button", { type: "button", className: "appshell-expand", "aria-label": expandLabel, onClick: () => setCollapsed(false), children: _jsx(PanelLeft, { size: 15, strokeWidth: 1.75 }) })), children] })] }));
66
+ }, children: [_jsx("div", { className: "appshell-side-inner", style: { width }, children: sidebar(toggleCollapse) }), !collapsed && (_jsx("div", { className: "appshell-resize", "data-dragging": dragging ? "" : undefined, "aria-hidden": "true", onPointerDown: startResize }))] }), _jsxs("div", { className: "appshell-main", children: [collapsed && (_jsx("button", { type: "button", className: "appshell-expand", "aria-label": expandLabel, onClick: () => setCollapsed(false), children: _jsx(PanelLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }) })), children] })] }));
67
67
  }
68
68
  AppShell.displayName = "AppShell";
@@ -1,4 +1,4 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c } from "../../tokens/index.js";
3
3
  // App-shell layout: a collapsible + resizable sidebar pane next to a main pane,
4
4
  // mirroring how Attio's window works. The sidebar pane clips (never squishes)
@@ -15,6 +15,8 @@ const config = defineRecipe({
15
15
  & .appshell-main { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
16
16
  & .appshell-expand { position: absolute; top: ${c.space["2"]}; inset-inline-start: ${c.space["2"]}; z-index: 6; display: inline-flex; align-items: center; justify-content: center; width: ${c.size.row}; height: ${c.size.row}; padding: 0; border: none; border-radius: ${c.radius.md}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}; background: ${c.bgSurface}; color: ${c.fgMuted}; font-family: inherit; cursor: pointer; transition: background-color .12s; }
17
17
  & .appshell-expand:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
18
+
19
+ ${rtlFlipCss}
18
20
  `,
19
21
  variants: {},
20
22
  defaultVariants: {},
@@ -946,7 +946,7 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
946
946
  }
947
947
  : undefined, onDragEnd: () => setDragCol(null), onClick: (e) => openColumnMenu(col.key, e.currentTarget), children: [Icon ? _jsx(Icon, { size: 14, strokeWidth: 1.75 }) : null, _jsx("span", { className: "recordstable-th-label", children: col.label }), _jsx("span", { className: "recordstable-sort", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: _jsx("path", { d: "m4 6 4 4 4-4" }) }) })] })), openMenu === `col:${col.key}` &&
948
948
  rootRef.current &&
949
- createPortal(_jsxs("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [_jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [_jsx(ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [_jsx(ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [_jsx(ArrowLeft, { size: 15, strokeWidth: 1.75 }), t.moveLeft] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [_jsx(ArrowRight, { size: 15, strokeWidth: 1.75 }), t.moveRight] }), _jsx("div", { className: "recordstable-menu-sep" }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
949
+ createPortal(_jsxs("div", { ref: menuRef, className: "recordstable-menu recordstable-menu--fixed recordstable-menu--hdr", role: "menu", "aria-label": `${col.label} column`, style: menuPos ?? undefined, onKeyDown: onMenuKeyDown, children: [_jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "asc"), children: [_jsx(ArrowUp, { size: 15, strokeWidth: 1.75 }), t.sortAscending] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => sortBy(col.key, "desc"), children: [_jsx(ArrowDown, { size: 15, strokeWidth: 1.75 }), t.sortDescending] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i <= 1, onClick: () => moveColumn(col.key, -1), children: [_jsx(ArrowLeft, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveLeft] }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, disabled: i === 0 || i >= cols.length - 1, onClick: () => moveColumn(col.key, 1), children: [_jsx(ArrowRight, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }), t.moveRight] }), _jsx("div", { className: "recordstable-menu-sep" }), _jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => {
950
950
  setRenamingCol(col.key);
951
951
  setOpenMenu(null);
952
952
  }, children: [_jsx(Pencil, { size: 15, strokeWidth: 1.75 }), t.editColumnLabel] }), cols.length > 1 && (_jsxs("button", { type: "button", className: "recordstable-menu-item", role: "menuitem", tabIndex: -1, onClick: () => hideColumn(col.key), children: [_jsx(EyeOff, { size: 15, strokeWidth: 1.75 }), t.hideFromView] }))] }), rootRef.current), _jsx("span", { className: "recordstable-resize", "aria-hidden": "true", onPointerDown: (e) => {
@@ -987,7 +987,7 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
987
987
  : undefined, avatarTone: avatarTone })), i === 0 && onOpenRecord && !isEditing && (_jsx("button", { type: "button", className: "recordstable-open", "aria-label": `Open ${cellText(col, row)}`, onClick: (e) => {
988
988
  e.stopPropagation();
989
989
  onOpenRecord(row);
990
- }, children: _jsx(ArrowUpRight, { size: 15, strokeWidth: 1.75 }) }))] }, col.key));
990
+ }, children: _jsx(ArrowUpRight, { "data-rtl-flip": "", size: 15, strokeWidth: 1.75 }) }))] }, col.key));
991
991
  }), _jsx("td", { className: "recordstable-td recordstable-addcol" })] }, row.id))) }), _jsx("tfoot", { children: _jsxs("tr", { children: [_jsx("td", { className: "recordstable-sum recordstable-col-check" }), cols.map((col, i) => {
992
992
  const a = agg[col.key] ?? "none";
993
993
  const res = summarize(col, a, visibleRows);
@@ -1,5 +1,5 @@
1
1
  import { toolbarChromeBody, toolbarChromeHead, } from "../../components/tabletoolbar/tabletoolbar.chrome.js";
2
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
2
+ import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
3
3
  import { contract as c } from "../../tokens/index.js";
4
4
  import { recordTagCss } from "./recordstable.tag.js";
5
5
  // Attio's record grid, measured: 36px rows, 1px borderSubtle (#eeeff1) grid
@@ -142,6 +142,8 @@ ${toolbarChromeBody("recordstable")}
142
142
  & .recordstable-bulk-sep { width: 1px; height: 20px; margin: 0 ${c.space["0.5"]}; background: ${c.borderSubtle}; }
143
143
  & .recordstable-bulk-close { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: ${c.radius.md}; background: transparent; color: ${c.fgMuted}; cursor: pointer; }
144
144
  & .recordstable-bulk-close:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
145
+
146
+ ${rtlFlipCss}
145
147
  `,
146
148
  variants: {},
147
149
  defaultVariants: {},
@@ -18,6 +18,6 @@ export function SettingsShell({ title = "Settings", onBack, groups, activeItem,
18
18
  }))
19
19
  .filter((g) => g.items.length > 0)
20
20
  : groups;
21
- return (_jsxs("div", { className: settingsshellRecipe({ className }), ...rest, children: [_jsxs("nav", { className: "settingsshell-nav", "aria-label": navLabel, children: [_jsxs("button", { type: "button", className: "settingsshell-back", onClick: onBack, children: [_jsx(ChevronLeft, { size: 16, strokeWidth: 2 }), title] }), searchable && (_jsxs("div", { className: "settingsshell-search", children: [_jsx(Search, { size: 15, strokeWidth: 1.75 }), _jsx("input", { type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value) })] })), _jsx("div", { className: "settingsshell-groups", children: filtered.length === 0 ? (_jsx("div", { className: "settingsshell-empty", children: emptyLabel })) : (filtered.map((g, gi) => (_jsxs("div", { className: "settingsshell-group", children: [g.label != null && (_jsx("div", { className: "settingsshell-group-label", children: g.label })), g.items.map((it) => (_jsxs("button", { type: "button", className: "settingsshell-item", "aria-current": it.id === activeItem ? "page" : undefined, "data-active": it.id === activeItem ? "" : undefined, onClick: () => onSelect(it.id), children: [it.icon, _jsx("span", { className: "settingsshell-item-label", children: it.label })] }, it.id)))] }, gi)))) })] }), _jsx("div", { className: "settingsshell-content", children: children })] }));
21
+ return (_jsxs("div", { className: settingsshellRecipe({ className }), ...rest, children: [_jsxs("nav", { className: "settingsshell-nav", "aria-label": navLabel, children: [_jsxs("button", { type: "button", className: "settingsshell-back", onClick: onBack, children: [_jsx(ChevronLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 2 }), title] }), searchable && (_jsxs("div", { className: "settingsshell-search", children: [_jsx(Search, { size: 15, strokeWidth: 1.75 }), _jsx("input", { type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value) })] })), _jsx("div", { className: "settingsshell-groups", children: filtered.length === 0 ? (_jsx("div", { className: "settingsshell-empty", children: emptyLabel })) : (filtered.map((g, gi) => (_jsxs("div", { className: "settingsshell-group", children: [g.label != null && (_jsx("div", { className: "settingsshell-group-label", children: g.label })), g.items.map((it) => (_jsxs("button", { type: "button", className: "settingsshell-item", "aria-current": it.id === activeItem ? "page" : undefined, "data-active": it.id === activeItem ? "" : undefined, onClick: () => onSelect(it.id), children: [it.icon, _jsx("span", { className: "settingsshell-item-label", children: it.label })] }, it.id)))] }, gi)))) })] }), _jsx("div", { className: "settingsshell-content", children: children })] }));
22
22
  }
23
23
  SettingsShell.displayName = "SettingsShell";
@@ -1,4 +1,4 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c } from "../../tokens/index.js";
3
3
  // The Settings layout — measured from Attio's Settings: a 256px left nav with a
4
4
  // back header, a search field, and grouped nav items (active row raised), beside
@@ -34,7 +34,9 @@ const config = defineRecipe({
34
34
  & .settingsshell-item[data-active] svg { color: ${c.fgPrimary}; }
35
35
  & .settingsshell-empty { padding: ${c.space["3"]} ${c.space["2"]}; font-size: ${c.text.md}; color: ${c.fgMuted}; }
36
36
 
37
- & .settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: ${c.space["6"]} ${c.space["8"]}; }`,
37
+ & .settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: ${c.space["6"]} ${c.space["8"]}; }
38
+ ${rtlFlipCss}
39
+ `,
38
40
  variants: {},
39
41
  defaultVariants: {},
40
42
  compoundVariants: [],
@@ -198,6 +198,8 @@
198
198
  & .recordstable-bulk-close { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: var(--radius-md); background: transparent; color: var(--fg-muted); cursor: pointer; }
199
199
  & .recordstable-bulk-close:hover { background: var(--bg-raised); color: var(--fg-primary); }
200
200
 
201
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
202
+
201
203
  }
202
204
 
203
205
  @media (prefers-reduced-motion: reduce) {
@@ -29,6 +29,8 @@
29
29
  & .settingsshell-empty { padding: var(--space-3) var(--space-2); font-size: var(--text-md); color: var(--fg-muted); }
30
30
 
31
31
  & .settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: var(--space-6) var(--space-8); }
32
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
33
+
32
34
  }
33
35
 
34
36
  @media (prefers-reduced-motion: reduce) {
package/dist/styles.css CHANGED
@@ -272,6 +272,8 @@
272
272
  & button.breadcrumb-item { border: none; background: none; font: inherit; cursor: pointer; }
273
273
  & .breadcrumb-item[aria-current] { color: var(--fg-primary); font-weight: var(--weight-medium); }
274
274
  & .breadcrumb-sep { flex-shrink: 0; color: color-mix(in srgb, var(--fg-primary) 28%, transparent); }
275
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
276
+
275
277
  }
276
278
 
277
279
  .bulkbar {
@@ -1732,6 +1734,8 @@
1732
1734
  & .appshell-expand { position: absolute; top: var(--space-2); inset-inline-start: var(--space-2); z-index: 6; display: inline-flex; align-items: center; justify-content: center; width: var(--size-row); height: var(--size-row); padding: 0; border: none; border-radius: var(--radius-md); box-shadow: inset 0 0 0 1px var(--border-subtle); background: var(--bg-surface); color: var(--fg-muted); font-family: inherit; cursor: pointer; transition: background-color .12s; }
1733
1735
  & .appshell-expand:hover { background: var(--bg-raised); color: var(--fg-primary); }
1734
1736
 
1737
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
1738
+
1735
1739
  }
1736
1740
 
1737
1741
  @media (prefers-reduced-motion: reduce) {
@@ -2449,6 +2453,8 @@
2449
2453
  & .recordstable-bulk-close { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: var(--radius-md); background: transparent; color: var(--fg-muted); cursor: pointer; }
2450
2454
  & .recordstable-bulk-close:hover { background: var(--bg-raised); color: var(--fg-primary); }
2451
2455
 
2456
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2457
+
2452
2458
  }
2453
2459
 
2454
2460
  @media (prefers-reduced-motion: reduce) {
@@ -2537,6 +2543,8 @@
2537
2543
  & .settingsshell-empty { padding: var(--space-3) var(--space-2); font-size: var(--text-md); color: var(--fg-muted); }
2538
2544
 
2539
2545
  & .settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: var(--space-6) var(--space-8); }
2546
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2547
+
2540
2548
  }
2541
2549
 
2542
2550
  @media (prefers-reduced-motion: reduce) {
@@ -0,0 +1 @@
1
+ export * from "./timepicker";
@@ -0,0 +1,15 @@
1
+ import { type MinuteStep, type TimePickerValue, type UseTimePickerProps } from "@seed-design/react-time-picker";
2
+ import type * as React from "react";
3
+ export type { MinuteStep, TimePickerValue };
4
+ export type TimePickerProps = UseTimePickerProps & Omit<React.ComponentPropsWithoutRef<"div">, keyof UseTimePickerProps | "children"> & {
5
+ /** Accessible names for the wheels, so they can be translated. */
6
+ labels?: Partial<Record<"period" | "hour" | "minute", string>>;
7
+ size?: "small" | "medium";
8
+ };
9
+ /**
10
+ * An hour/minute wheel, driven by seed-design's headless engine so the column
11
+ * order, the 12- vs 24-hour split and the minute stepping are locale-correct by
12
+ * construction, and rendered with crisp's own `WheelPicker` so the pixels are
13
+ * the ones already measured for it.
14
+ */
15
+ export declare function TimePicker({ value, defaultValue, onValueChange, minuteStep, locale, disabled, labels, size, className, ...rest }: TimePickerProps): React.JSX.Element;
@@ -2,4 +2,5 @@ export { cx } from "./cx";
2
2
  export { defineRecipe } from "./define";
3
3
  export { Primitive } from "./primitive";
4
4
  export { resolveRecipe } from "./resolve";
5
+ export * from "./rtl";
5
6
  export type * from "./types";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mirroring for direction-bearing icons.
3
+ *
4
+ * A chevron or arrow that means "back", "next", "toward the start" or "open"
5
+ * points the wrong way under RTL, and unlike spacing or alignment it cannot be
6
+ * fixed with a logical property: these are `lucide-react` nodes, static SVG
7
+ * paths with no direction of their own.
8
+ *
9
+ * Mark the icons that carry a direction with `data-rtl-flip` and include this
10
+ * snippet in the recipe. Icons whose meaning is NOT directional — a downward
11
+ * disclosure chevron, a search glass, a check — must be left unmarked: flipping
12
+ * them is a regression, not a fix.
13
+ */
14
+ export declare const rtlFlipCss = "[dir=\"rtl\"] & [data-rtl-flip] { transform: scaleX(-1); }";
@@ -41,6 +41,7 @@ export * from "./components/table";
41
41
  export * from "./components/tabletoolbar";
42
42
  export * from "./components/tabs";
43
43
  export * from "./components/textarea";
44
+ export * from "./components/timepicker";
44
45
  export * from "./components/toast";
45
46
  export * from "./components/tooltip";
46
47
  export * from "./components/wheelpicker";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -247,6 +247,11 @@
247
247
  "import": "./dist/esm/components/textarea/index.js",
248
248
  "require": "./dist/cjs/components/textarea/index.js"
249
249
  },
250
+ "./timepicker": {
251
+ "types": "./dist/types/components/timepicker/index.d.ts",
252
+ "import": "./dist/esm/components/timepicker/index.js",
253
+ "require": "./dist/cjs/components/timepicker/index.js"
254
+ },
250
255
  "./toast": {
251
256
  "types": "./dist/types/components/toast/index.d.ts",
252
257
  "import": "./dist/esm/components/toast/index.js",
@@ -413,6 +418,7 @@
413
418
  "@seed-design/dom-utils": "^2.1.0",
414
419
  "@seed-design/react-date-picker": "1.2.0",
415
420
  "@seed-design/react-file-upload": "1.0.1",
421
+ "@seed-design/react-time-picker": "1.0.0",
416
422
  "@zag-js/combobox": "^1.43.3",
417
423
  "@zag-js/dialog": "^1.43.3",
418
424
  "@zag-js/react": "^1.43.3",