@stll/ui 0.25.2 → 0.26.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.
@@ -10,7 +10,7 @@
10
10
  */
11
11
  declare const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
12
12
  declare const buttonVariants: (props?: ({
13
- size?: "default" | "chip" | "icon" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "sm" | "xl" | "xs" | null | undefined;
13
+ size?: "default" | "sm" | "lg" | "chip" | "icon" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "xl" | "xs" | null | undefined;
14
14
  variant?: "link" | "default" | "destructive" | "destructive-outline" | "ghost" | "outline" | "secondary" | null | undefined;
15
15
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
16
16
  //#endregion
@@ -31,17 +31,27 @@ type ColorPickerProps = {
31
31
  align?: "start" | "center" | "end";
32
32
  className?: string;
33
33
  };
34
- type ColorPickerContentProps = {
34
+ type ColorPickerContentBaseProps = {
35
35
  value?: string | undefined;
36
36
  onSelect?: ((value: string) => void) | undefined;
37
- onClear?: (() => void) | undefined;
38
37
  presets: ColorPreset[];
39
- columns: number;
40
- defaultExpanded: boolean;
41
38
  moreLabel: string;
42
39
  };
40
+ type ColorPickerContentProps = ColorPickerContentBaseProps & ({
41
+ columns: number;
42
+ defaultExpanded: boolean;
43
+ onClear?: (() => void) | undefined;
44
+ /** Popover content closes on preset selection. */
45
+ presentation?: "popover";
46
+ } | {
47
+ columns?: never;
48
+ defaultExpanded?: never;
49
+ onClear?: never;
50
+ /** Inline content stays mounted and reserves its popup for custom color. */
51
+ presentation: "inline";
52
+ });
43
53
  declare const DEFAULT_PRESETS: ColorPreset[];
44
- declare const ColorPickerContent: ({ value, onSelect, onClear, presets, columns, defaultExpanded, moreLabel }: ColorPickerContentProps) => React$1.JSX.Element;
54
+ declare const ColorPickerContent: ({ value, onSelect, onClear, presets, columns, defaultExpanded, moreLabel, presentation }: ColorPickerContentProps) => React$1.JSX.Element;
45
55
  declare const ColorPicker: ({ value, onSelect, onClear, presets, columns, defaultExpanded, moreLabel, children, side, align, className }: ColorPickerProps) => React$1.JSX.Element;
46
56
  //#endregion
47
57
  export { ColorPicker, ColorPickerContent, type ColorPickerContentProps, type ColorPickerProps, type ColorPreset, DEFAULT_PRESETS };
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/utils.js";
3
+ import { OVERLAY_LAYER_CLASS_NAMES } from "../lib/overlay-layer.js";
3
4
  import { CheckIcon, ChevronDownIcon } from "lucide-react";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
6
  import { Suspense, lazy, useState } from "react";
6
7
  import { Popover } from "@base-ui/react/popover";
7
8
  //#region src/components/color-picker.tsx
@@ -112,25 +113,97 @@ const isLightHex = (hex) => {
112
113
  const b = Number.parseInt(hex.slice(4, 6), 16);
113
114
  return (r * 299 + g * 587 + b * 114) / 1e3 > 220;
114
115
  };
116
+ const checkIconColorClassNames = {
117
+ dark: "text-(--color-white)",
118
+ light: "text-(--color-black)"
119
+ };
115
120
  /** Check if a value looks like a 6-char hex (no CSS vars, no named colors). */
116
121
  const looksLikeHex = (v) => /^[0-9A-Fa-f]{6}$/u.test(v);
117
- const ColorSwatch = ({ cssColor, selected, label, isLight, onClick }) => /* @__PURE__ */ jsx(Popover.Close, {
118
- render: /* @__PURE__ */ jsx("button", {
122
+ const ColorSwatch = ({ cssColor, selected, label, isLight, onClick, presentation }) => {
123
+ let selectionClassName = "border-border/40";
124
+ if (selected) selectionClassName = presentation === "inline" ? "border-transparent ring-2 ring-ring" : "border-foreground ring-ring/24 ring-1";
125
+ const swatch = /* @__PURE__ */ jsx("button", {
119
126
  "aria-label": label,
120
- className: cn("hover:border-foreground relative flex size-6 items-center justify-center rounded-md border transition-[transform,border-color] hover:scale-115 sm:size-5", selected ? "border-foreground ring-ring/24 ring-1" : "border-border/40", isLight && !selected && "border-border"),
127
+ "aria-pressed": selected,
128
+ className: cn(presentation === "inline" ? "ring-offset-popover relative grid size-11 shrink-0 place-items-center rounded-full border ring-offset-2 transition-transform outline-none hover:scale-105 focus-visible:ring-2" : "hover:border-foreground relative flex size-6 items-center justify-center rounded-md border transition-[transform,border-color] hover:scale-115 sm:size-5", selectionClassName, isLight && !selected && "border-border"),
121
129
  onClick,
122
130
  style: { backgroundColor: cssColor },
123
- type: "button"
131
+ type: "button",
132
+ children: selected && /* @__PURE__ */ jsx(CheckIcon, { className: cn("pointer-events-none", presentation === "inline" ? "bg-background/88 text-foreground size-5 rounded-full p-0.5 shadow-sm" : "size-3 sm:size-2.5", presentation === "popover" && checkIconColorClassNames[isLight ? "light" : "dark"]) })
133
+ });
134
+ return presentation === "inline" ? swatch : /* @__PURE__ */ jsx(Popover.Close, { render: swatch });
135
+ };
136
+ const CustomColorControls = ({ handleInputChange, handlePickerChange, inputHex, pickerHex }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Suspense, {
137
+ fallback: /* @__PURE__ */ jsx("div", {
138
+ "aria-hidden": true,
139
+ className: "ring-border/30 h-[140px] w-full rounded-lg ring-1"
124
140
  }),
125
- children: selected && /* @__PURE__ */ jsx(CheckIcon, {
126
- className: "pointer-events-none size-3 sm:size-2.5",
127
- style: { color: isLight ? "#000" : "#fff" }
141
+ children: /* @__PURE__ */ jsx(HexColorPicker, {
142
+ className: "ring-border/30 !h-[140px] !w-full overflow-hidden rounded-lg ring-1",
143
+ color: pickerHex,
144
+ onChange: (hex) => handlePickerChange(hex.replace("#", ""))
128
145
  })
129
- });
130
- const ColorPickerContent = ({ value, onSelect, onClear, presets, columns, defaultExpanded, moreLabel }) => {
146
+ }), /* @__PURE__ */ jsxs("div", {
147
+ className: "flex items-center gap-1.5",
148
+ children: [
149
+ /* @__PURE__ */ jsx("span", {
150
+ className: "text-muted-foreground text-[11px]",
151
+ children: "#"
152
+ }),
153
+ /* @__PURE__ */ jsx("input", {
154
+ "aria-label": "Custom hex color",
155
+ className: "border-input bg-background text-foreground h-6 flex-1 rounded border px-1.5 font-mono text-[11px] outline-none",
156
+ dir: "ltr",
157
+ maxLength: 6,
158
+ onChange: (event) => handleInputChange(event.target.value),
159
+ onKeyDown: (event) => {
160
+ if (event.key !== "Escape") event.stopPropagation();
161
+ },
162
+ onMouseDown: (event) => event.stopPropagation(),
163
+ onPointerDown: (event) => event.stopPropagation(),
164
+ placeholder: "FF0000",
165
+ value: inputHex
166
+ }),
167
+ isValidHex(inputHex) && /* @__PURE__ */ jsx("span", {
168
+ className: "border-border size-6 shrink-0 rounded border",
169
+ style: { backgroundColor: `#${inputHex}` }
170
+ })
171
+ ]
172
+ })] });
173
+ const InlineCustomColor = ({ customSelected, handleInputChange, handlePickerChange, inputHex, label, pickerHex, presets, value }) => {
174
+ const customColor = customSelected ? `#${value}` : void 0;
175
+ const customGradient = `conic-gradient(${presets.map((preset) => swatchColor(preset)).join(", ")})`;
176
+ return /* @__PURE__ */ jsxs(Popover.Root, { children: [/* @__PURE__ */ jsx(Popover.Trigger, {
177
+ render: /* @__PURE__ */ jsx("button", {
178
+ "aria-label": label,
179
+ "aria-pressed": customSelected,
180
+ className: cn("ring-offset-popover relative grid size-11 shrink-0 place-items-center rounded-full border border-transparent ring-offset-2 transition-transform outline-none hover:scale-105 focus-visible:ring-2", customSelected && "ring-ring ring-2"),
181
+ style: customColor ? { backgroundColor: customColor } : { backgroundImage: customGradient },
182
+ type: "button"
183
+ }),
184
+ children: customSelected ? /* @__PURE__ */ jsx(CheckIcon, { className: "bg-background/88 text-foreground pointer-events-none size-5 rounded-full p-0.5 shadow-sm" }) : null
185
+ }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
186
+ align: "end",
187
+ className: OVERLAY_LAYER_CLASS_NAMES.popup,
188
+ side: "bottom",
189
+ sideOffset: 4,
190
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
191
+ className: "bg-popover text-popover-foreground flex w-56 flex-col gap-2 rounded-lg border p-2 shadow-lg/5",
192
+ "data-slot": "color-picker-custom-popup",
193
+ children: /* @__PURE__ */ jsx(CustomColorControls, {
194
+ handleInputChange,
195
+ handlePickerChange,
196
+ inputHex,
197
+ pickerHex
198
+ })
199
+ })
200
+ }) })] });
201
+ };
202
+ const ColorPickerContent = ({ value, onSelect, onClear, presets, columns, defaultExpanded, moreLabel, presentation = "popover" }) => {
131
203
  const [expanded, setExpanded] = useState(defaultExpanded);
132
204
  const [pickerHex, setPickerHex] = useState(() => (looksLikeHex(value ?? "") ? value : "000000") ?? "000000");
133
205
  const [inputHex, setInputHex] = useState("");
206
+ const customSelected = !presets.some((preset) => preset.value === value) && looksLikeHex(value ?? "");
134
207
  /** Called when the visual picker (SB square / hue strip) emits a color. */
135
208
  const handlePickerChange = (hex) => {
136
209
  const normalized = normalizeHex(hex);
@@ -147,6 +220,27 @@ const ColorPickerContent = ({ value, onSelect, onClear, presets, columns, defaul
147
220
  onSelect?.(cleaned);
148
221
  }
149
222
  };
223
+ if (presentation === "inline") return /* @__PURE__ */ jsxs("div", {
224
+ className: "flex items-center gap-1",
225
+ "data-slot": "color-picker",
226
+ children: [presets.map((preset) => /* @__PURE__ */ jsx(ColorSwatch, {
227
+ cssColor: swatchColor(preset),
228
+ isLight: looksLikeHex(preset.value) && isLightHex(preset.value),
229
+ label: preset.label,
230
+ onClick: () => onSelect?.(preset.value),
231
+ presentation: "inline",
232
+ selected: value === preset.value
233
+ }, preset.value)), /* @__PURE__ */ jsx(InlineCustomColor, {
234
+ customSelected,
235
+ handleInputChange,
236
+ handlePickerChange,
237
+ inputHex,
238
+ label: moreLabel,
239
+ pickerHex,
240
+ presets,
241
+ value
242
+ })]
243
+ });
150
244
  return /* @__PURE__ */ jsxs("div", {
151
245
  className: "flex flex-col gap-1.5",
152
246
  "data-slot": "color-picker",
@@ -172,6 +266,7 @@ const ColorPickerContent = ({ value, onSelect, onClear, presets, columns, defaul
172
266
  isLight: looksLikeHex(preset.value) && isLightHex(preset.value),
173
267
  label: preset.label,
174
268
  onClick: () => onSelect?.(preset.value),
269
+ presentation: "popover",
175
270
  selected: value === preset.value
176
271
  }, preset.value))
177
272
  }),
@@ -180,43 +275,14 @@ const ColorPickerContent = ({ value, onSelect, onClear, presets, columns, defaul
180
275
  onClick: () => setExpanded(true),
181
276
  type: "button",
182
277
  children: [moreLabel, /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3" })]
183
- }) : /* @__PURE__ */ jsxs("div", {
278
+ }) : /* @__PURE__ */ jsx("div", {
184
279
  className: "border-border flex flex-col gap-2 border-t pt-2",
185
- children: [/* @__PURE__ */ jsx(Suspense, {
186
- fallback: /* @__PURE__ */ jsx("div", {
187
- "aria-hidden": true,
188
- className: "ring-border/30 h-[140px] w-full rounded-lg ring-1"
189
- }),
190
- children: /* @__PURE__ */ jsx(HexColorPicker, {
191
- className: "ring-border/30 !h-[140px] !w-full overflow-hidden rounded-lg ring-1",
192
- color: pickerHex,
193
- onChange: (hex) => handlePickerChange(hex.replace("#", ""))
194
- })
195
- }), /* @__PURE__ */ jsxs("div", {
196
- className: "flex items-center gap-1.5",
197
- children: [
198
- /* @__PURE__ */ jsx("span", {
199
- className: "text-muted-foreground text-[11px]",
200
- children: "#"
201
- }),
202
- /* @__PURE__ */ jsx("input", {
203
- "aria-label": "Custom hex color",
204
- className: "border-input bg-background text-foreground h-6 flex-1 rounded border px-1.5 font-mono text-[11px] outline-none",
205
- dir: "ltr",
206
- maxLength: 6,
207
- onChange: (e) => handleInputChange(e.target.value),
208
- onKeyDown: (e) => e.stopPropagation(),
209
- onMouseDown: (e) => e.stopPropagation(),
210
- onPointerDown: (e) => e.stopPropagation(),
211
- placeholder: "FF0000",
212
- value: inputHex
213
- }),
214
- isValidHex(inputHex) && /* @__PURE__ */ jsx("span", {
215
- className: "border-border size-6 shrink-0 rounded border",
216
- style: { backgroundColor: `#${inputHex}` }
217
- })
218
- ]
219
- })]
280
+ children: /* @__PURE__ */ jsx(CustomColorControls, {
281
+ handleInputChange,
282
+ handlePickerChange,
283
+ inputHex,
284
+ pickerHex
285
+ })
220
286
  })
221
287
  ]
222
288
  });
@@ -228,7 +294,7 @@ const ColorPicker = ({ value, onSelect, onClear, presets = DEFAULT_PRESETS, colu
228
294
  children
229
295
  }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
230
296
  align,
231
- className: "z-50",
297
+ className: OVERLAY_LAYER_CLASS_NAMES.popup,
232
298
  side,
233
299
  sideOffset: 4,
234
300
  children: /* @__PURE__ */ jsx(Popover.Popup, {
@@ -240,6 +306,7 @@ const ColorPicker = ({ value, onSelect, onClear, presets = DEFAULT_PRESETS, colu
240
306
  moreLabel,
241
307
  onClear,
242
308
  onSelect,
309
+ presentation: "popover",
243
310
  presets,
244
311
  value
245
312
  })
@@ -5,7 +5,7 @@ import { VariantProps } from "class-variance-authority";
5
5
  //#region src/components/input-group.d.ts
6
6
  declare const InputGroup: ({ className, ...props }: React$1.ComponentProps<"div">) => React$1.JSX.Element;
7
7
  declare const inputGroupAddonVariants: (props?: ({
8
- align?: "inline-end" | "inline-start" | "block-end" | "block-start" | null | undefined;
8
+ align?: "inline-end" | "inline-start" | "block-start" | "block-end" | null | undefined;
9
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
10
  declare const InputGroupAddon: ({ className, align, ...props }: React$1.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) => React$1.JSX.Element;
11
11
  declare const InputGroupText: ({ className, ...props }: React$1.ComponentProps<"span">) => React$1.JSX.Element;
@@ -30,7 +30,7 @@ declare function useSidebar(): SidebarContextProps;
30
30
  * left for the content column. Mobile reports 0: there the sidebar is an
31
31
  * overlay sheet and occupies no layout width.
32
32
  */
33
- declare function useSidebarInlineSize(): 0 | 256 | 48;
33
+ declare function useSidebarInlineSize(): 0 | 48 | 256;
34
34
  declare const SidebarProvider: ({ defaultOpen, forceCollapsed, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }: React.ComponentProps<"div"> & {
35
35
  defaultOpen?: boolean;
36
36
  forceCollapsed?: boolean;
@@ -83,7 +83,7 @@ declare const SidebarMenu: ({ className, ...props }: React.ComponentProps<"ul">)
83
83
  declare const SidebarMenuItem: ({ className, ...props }: React.ComponentProps<"li">) => import("react").JSX.Element;
84
84
  declare const sidebarMenuButtonVariants: (props?: ({
85
85
  variant?: "default" | "outline" | null | undefined;
86
- size?: "default" | "lg" | "sm" | "rail" | null | undefined;
86
+ size?: "default" | "sm" | "lg" | "rail" | null | undefined;
87
87
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
88
88
  declare const SidebarMenuButton: ({ asChild, isActive, variant, size, tooltip, className, ...props }: React.ComponentProps<"button"> & {
89
89
  asChild?: boolean;
@@ -8,7 +8,7 @@ import { Tabs } from "@base-ui/react/tabs";
8
8
  */
9
9
  declare const InspectorTabs: ({ className, ...props }: Omit<Tabs.Root.Props, "orientation">) => React$1.JSX.Element;
10
10
  declare const INSPECTOR_RAIL_MEDIA_QUERY: "(min-width: 48rem)";
11
- declare const resolveInspectorTabOrientation: (isRailLayout: boolean) => "vertical" | "horizontal";
11
+ declare const resolveInspectorTabOrientation: (isRailLayout: boolean) => "horizontal" | "vertical";
12
12
  declare const InspectorTabList: ({ className, ...props }: Tabs.List.Props) => React$1.JSX.Element;
13
13
  declare const InspectorTab: ({ className, ...props }: Tabs.Tab.Props) => React$1.JSX.Element;
14
14
  declare const InspectorTabPanel: ({ className, ...props }: Tabs.Panel.Props) => React$1.JSX.Element;
@@ -5,6 +5,6 @@ declare const CONTROL_SIZE: Readonly<{
5
5
  readonly lg: "lg";
6
6
  }>;
7
7
  type ControlSize = (typeof CONTROL_SIZE)[keyof typeof CONTROL_SIZE];
8
- declare const CONTROL_SIZES: readonly ("default" | "lg" | "sm")[];
8
+ declare const CONTROL_SIZES: readonly ("default" | "sm" | "lg")[];
9
9
  //#endregion
10
10
  export { CONTROL_SIZE, CONTROL_SIZES, type ControlSize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/ui",
3
- "version": "0.25.2",
3
+ "version": "0.26.0",
4
4
  "description": "Stella's design system: bidi-aware React primitives built on Base UI, the dockable inspector pane, and the Tailwind v4 theme they are styled with.",
5
5
  "keywords": [
6
6
  "base-ui",