@alpic-ai/ui 0.0.0-dev.f8c735b → 0.0.0-dev.f936359

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 (63) hide show
  1. package/dist/components/accordion-card.d.mts +1 -1
  2. package/dist/components/accordion.d.mts +1 -1
  3. package/dist/components/alert.d.mts +1 -1
  4. package/dist/components/attachment-tile.mjs +1 -1
  5. package/dist/components/avatar.d.mts +1 -1
  6. package/dist/components/breadcrumb.d.mts +1 -1
  7. package/dist/components/button.d.mts +4 -2
  8. package/dist/components/button.mjs +20 -6
  9. package/dist/components/card.d.mts +1 -1
  10. package/dist/components/checkbox.d.mts +1 -1
  11. package/dist/components/collapsible.d.mts +1 -1
  12. package/dist/components/combobox.d.mts +1 -1
  13. package/dist/components/combobox.mjs +1 -1
  14. package/dist/components/command.d.mts +1 -1
  15. package/dist/components/copyable.d.mts +1 -1
  16. package/dist/components/copyable.mjs +1 -1
  17. package/dist/components/description-list.d.mts +1 -1
  18. package/dist/components/dialog.d.mts +1 -1
  19. package/dist/components/dropdown-menu.d.mts +1 -1
  20. package/dist/components/form.d.mts +1 -1
  21. package/dist/components/form.mjs +1 -1
  22. package/dist/components/github-button.d.mts +13 -0
  23. package/dist/components/github-button.mjs +24 -0
  24. package/dist/components/input-group.d.mts +1 -1
  25. package/dist/components/input.d.mts +1 -1
  26. package/dist/components/input.mjs +1 -1
  27. package/dist/components/label.d.mts +1 -1
  28. package/dist/components/page-loader.d.mts +11 -0
  29. package/dist/components/page-loader.mjs +122 -0
  30. package/dist/components/pagination.d.mts +1 -1
  31. package/dist/components/popover.d.mts +1 -1
  32. package/dist/components/radio-group.d.mts +1 -1
  33. package/dist/components/scroll-area.d.mts +1 -1
  34. package/dist/components/select.d.mts +1 -1
  35. package/dist/components/separator.d.mts +1 -1
  36. package/dist/components/sheet.d.mts +1 -1
  37. package/dist/components/sidebar.d.mts +1 -1
  38. package/dist/components/sidebar.mjs +63 -19
  39. package/dist/components/sonner.d.mts +1 -1
  40. package/dist/components/switch.d.mts +1 -1
  41. package/dist/components/table.d.mts +11 -2
  42. package/dist/components/table.mjs +2 -2
  43. package/dist/components/tabs.d.mts +1 -1
  44. package/dist/components/tabs.mjs +1 -1
  45. package/dist/components/textarea.d.mts +1 -1
  46. package/dist/components/textarea.mjs +1 -1
  47. package/dist/components/toggle-group.d.mts +1 -1
  48. package/dist/components/toggle-group.mjs +1 -1
  49. package/dist/components/tooltip-icon-button.mjs +1 -1
  50. package/dist/components/tooltip.d.mts +1 -1
  51. package/dist/components/typography.d.mts +1 -1
  52. package/package.json +10 -10
  53. package/src/components/button.tsx +13 -9
  54. package/src/components/combobox.tsx +18 -6
  55. package/src/components/github-button.tsx +34 -0
  56. package/src/components/page-loader.tsx +59 -0
  57. package/src/components/sidebar.tsx +59 -20
  58. package/src/components/table.tsx +16 -2
  59. package/src/hooks/use-copy-to-clipboard.ts +6 -2
  60. package/src/stories/button.stories.tsx +23 -1
  61. package/src/stories/sidebar.stories.tsx +6 -3
  62. package/src/stories/table.stories.tsx +2 -2
  63. package/src/styles/tokens.css +210 -0
@@ -1,15 +1,14 @@
1
1
  "use client";
2
- import { useIsMobile } from "../hooks/use-mobile.mjs";
3
2
  import { cn } from "../lib/cn.mjs";
4
3
  import { Button } from "./button.mjs";
5
4
  import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
6
5
  import { Separator } from "./separator.mjs";
7
6
  import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet.mjs";
7
+ import { useIsMobile } from "../hooks/use-mobile.mjs";
8
8
  import { Skeleton } from "./skeleton.mjs";
9
- import * as React from "react";
10
- import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
11
9
  import { jsx, jsxs } from "react/jsx-runtime";
12
10
  import { cva } from "class-variance-authority";
11
+ import * as React from "react";
13
12
  import { Slot } from "@radix-ui/react-slot";
14
13
  //#region src/components/sidebar.tsx
15
14
  const SIDEBAR_COOKIE_NAME = "sidebar_state";
@@ -18,6 +17,18 @@ const SIDEBAR_WIDTH = "15rem";
18
17
  const SIDEBAR_WIDTH_MOBILE = "16rem";
19
18
  const SIDEBAR_WIDTH_ICON = "3.5rem";
20
19
  const SIDEBAR_KEYBOARD_SHORTCUT = "b";
20
+ const INTERACTIVE_SIDEBAR_ELEMENT_SELECTOR = [
21
+ "a",
22
+ "button",
23
+ "input",
24
+ "select",
25
+ "textarea",
26
+ "[role='button']",
27
+ "[role='link']",
28
+ "[role='menuitem']",
29
+ "[contenteditable='true']",
30
+ "[tabindex]:not([tabindex='-1'])"
31
+ ].join(", ");
21
32
  const SidebarContext = React.createContext(null);
22
33
  function useSidebar() {
23
34
  const context = React.useContext(SidebarContext);
@@ -81,10 +92,20 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: set
81
92
  });
82
93
  }
83
94
  function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props }) {
84
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
95
+ const { isMobile, state, openMobile, setOpenMobile, open, setOpen } = useSidebar();
96
+ function handleSurfaceClickCapture(event) {
97
+ if (event.target instanceof Element && event.target.closest(INTERACTIVE_SIDEBAR_ELEMENT_SELECTOR)) return;
98
+ if (!open) {
99
+ event.preventDefault();
100
+ event.stopPropagation();
101
+ setOpen(true);
102
+ return;
103
+ }
104
+ setOpen(false);
105
+ }
85
106
  if (collapsible === "none") return /* @__PURE__ */ jsx("div", {
86
107
  "data-slot": "sidebar",
87
- className: cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className),
108
+ className: cn("bg-sidebar-surface text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className),
88
109
  ...props,
89
110
  children
90
111
  });
@@ -96,7 +117,7 @@ function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas"
96
117
  "data-sidebar": "sidebar",
97
118
  "data-slot": "sidebar",
98
119
  "data-mobile": "true",
99
- className: "bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
120
+ className: "bg-sidebar-surface text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
100
121
  style: { "--sidebar-width": SIDEBAR_WIDTH_MOBILE },
101
122
  side,
102
123
  children: [/* @__PURE__ */ jsxs(SheetHeader, {
@@ -120,20 +141,20 @@ function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas"
120
141
  className: cn("relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-out", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)")
121
142
  }), /* @__PURE__ */ jsx("div", {
122
143
  "data-slot": "sidebar-container",
123
- className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-out md:flex", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", className),
144
+ className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-out md:flex", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "border-sidebar-border group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", className),
124
145
  ...props,
125
146
  children: /* @__PURE__ */ jsx("div", {
126
147
  "data-sidebar": "sidebar",
127
148
  "data-slot": "sidebar-inner",
128
- className: "bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm",
149
+ className: "bg-sidebar-surface group-data-[variant=floating]:border-sidebar-border flex h-full w-full cursor-pointer flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm",
150
+ onClickCapture: handleSurfaceClickCapture,
129
151
  children
130
152
  })
131
153
  })]
132
154
  });
133
155
  }
134
156
  function SidebarTrigger({ className, onClick, ...props }) {
135
- const { state, isMobile, openMobile, toggleSidebar } = useSidebar();
136
- const isOpen = isMobile ? openMobile : state === "expanded";
157
+ const { toggleSidebar } = useSidebar();
137
158
  return /* @__PURE__ */ jsxs(Button, {
138
159
  "data-sidebar": "trigger",
139
160
  "data-slot": "sidebar-trigger",
@@ -145,12 +166,35 @@ function SidebarTrigger({ className, onClick, ...props }) {
145
166
  toggleSidebar();
146
167
  },
147
168
  ...props,
148
- children: [isOpen ? /* @__PURE__ */ jsx(PanelLeftClose, { className: "size-4.5" }) : /* @__PURE__ */ jsx(PanelLeftOpen, { className: "size-4.5" }), /* @__PURE__ */ jsx("span", {
169
+ children: [/* @__PURE__ */ jsx(SidebarToggleIcon, { className: "size-4.5" }), /* @__PURE__ */ jsx("span", {
149
170
  className: "sr-only",
150
171
  children: "Toggle Sidebar"
151
172
  })]
152
173
  });
153
174
  }
175
+ function SidebarToggleIcon({ className, ...props }) {
176
+ return /* @__PURE__ */ jsxs("svg", {
177
+ viewBox: "0 0 20 20",
178
+ fill: "none",
179
+ "aria-hidden": "true",
180
+ className,
181
+ ...props,
182
+ children: [/* @__PURE__ */ jsx("rect", {
183
+ x: "2.75",
184
+ y: "2.75",
185
+ width: "14.5",
186
+ height: "14.5",
187
+ rx: "4",
188
+ stroke: "currentColor",
189
+ strokeWidth: "1.5"
190
+ }), /* @__PURE__ */ jsx("path", {
191
+ d: "M10 4.75V15.25",
192
+ stroke: "currentColor",
193
+ strokeWidth: "1.5",
194
+ strokeLinecap: "round"
195
+ })]
196
+ });
197
+ }
154
198
  function SidebarRail({ className, ...props }) {
155
199
  const { toggleSidebar } = useSidebar();
156
200
  return /* @__PURE__ */ jsx("button", {
@@ -160,7 +204,7 @@ function SidebarRail({ className, ...props }) {
160
204
  tabIndex: -1,
161
205
  onClick: toggleSidebar,
162
206
  title: "Toggle Sidebar",
163
- className: cn("[@media(hover:hover)]:hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex", "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "[@media(hover:hover)]:hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", className),
207
+ className: cn("absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex", "[@media(hover:hover)]:hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", className),
164
208
  ...props
165
209
  });
166
210
  }
@@ -182,19 +226,19 @@ function SidebarHeader({ className, icon, title, children, ...props }) {
182
226
  return /* @__PURE__ */ jsxs("div", {
183
227
  "data-slot": "sidebar-header",
184
228
  "data-sidebar": "header",
185
- className: cn("flex flex-col gap-2 p-2", className),
229
+ className: cn("flex flex-col gap-2 py-2", className),
186
230
  ...props,
187
231
  children: [/* @__PURE__ */ jsxs("div", {
188
232
  className: "flex h-8 items-center gap-2 px-3",
189
233
  children: [
190
- /* @__PURE__ */ jsxs("div", {
191
- className: "relative shrink-0",
234
+ /* @__PURE__ */ jsxs("span", {
235
+ className: "relative flex size-8 shrink-0 items-center justify-center",
192
236
  children: [/* @__PURE__ */ jsx("span", {
193
- className: "transition-opacity group-data-[collapsible=icon]:group-hover:opacity-0",
237
+ className: "transition-opacity duration-200 group-data-[collapsible=icon]:group-hover:opacity-0",
194
238
  children: icon
195
- }), /* @__PURE__ */ jsx("div", {
196
- className: "absolute inset-0 flex items-center justify-center opacity-0 transition-opacity group-data-[collapsible=icon]:group-hover:opacity-100",
197
- children: /* @__PURE__ */ jsx(SidebarTrigger, {})
239
+ }), /* @__PURE__ */ jsx(SidebarTrigger, {
240
+ tabIndex: -1,
241
+ className: "absolute inset-0 opacity-0 transition-opacity duration-200 group-data-[collapsible=icon]:group-hover:opacity-100"
198
242
  })]
199
243
  }),
200
244
  /* @__PURE__ */ jsx("span", {
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as React from "react";
3
3
  import { Toaster as Toaster$1, toast } from "sonner";
4
4
 
5
5
  //#region src/components/sonner.d.ts
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as React from "react";
3
3
  import * as SwitchPrimitive from "@radix-ui/react-switch";
4
4
 
5
5
  //#region src/components/switch.d.ts
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as React from "react";
3
3
 
4
4
  //#region src/components/table.d.ts
5
5
  declare function Table({
@@ -26,10 +26,19 @@ declare function TableHead({
26
26
  className,
27
27
  ...props
28
28
  }: React.ComponentProps<"th">): _$react_jsx_runtime0.JSX.Element;
29
+ interface TableCellProps extends React.ComponentProps<"td"> {
30
+ /**
31
+ * When true, the cell renders edge-to-edge so the child can act as the
32
+ * interactive surface (e.g. a button or popover trigger filling the cell).
33
+ * Defaults to false (standard padded cell).
34
+ */
35
+ interactive?: boolean;
36
+ }
29
37
  declare function TableCell({
30
38
  className,
39
+ interactive,
31
40
  ...props
32
- }: React.ComponentProps<"td">): _$react_jsx_runtime0.JSX.Element;
41
+ }: TableCellProps): _$react_jsx_runtime0.JSX.Element;
33
42
  declare function TableCaption({
34
43
  className,
35
44
  ...props
@@ -47,10 +47,10 @@ function TableHead({ className, ...props }) {
47
47
  ...props
48
48
  });
49
49
  }
50
- function TableCell({ className, ...props }) {
50
+ function TableCell({ className, interactive = false, ...props }) {
51
51
  return /* @__PURE__ */ jsx("td", {
52
52
  "data-slot": "table-cell",
53
- className: cn("px-6 py-2 align-middle", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pr-3", className),
53
+ className: cn("align-middle", interactive ? "h-px p-0" : "px-6 py-2", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pr-3", className),
54
54
  ...props
55
55
  });
56
56
  }
@@ -1,6 +1,6 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
3
2
  import { VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
4
  import * as TabsPrimitive from "@radix-ui/react-tabs";
5
5
  import * as _$class_variance_authority_types0 from "class-variance-authority/types";
6
6
 
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.mjs";
3
- import { createContext, use } from "react";
4
3
  import { jsx } from "react/jsx-runtime";
5
4
  import { cva } from "class-variance-authority";
5
+ import { createContext, use } from "react";
6
6
  import { Slot } from "@radix-ui/react-slot";
7
7
  import * as TabsPrimitive from "@radix-ui/react-tabs";
8
8
  //#region src/components/tabs.tsx
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as React from "react";
3
3
 
4
4
  //#region src/components/textarea.d.ts
5
5
  interface TextareaProps extends React.ComponentProps<"textarea"> {
@@ -2,9 +2,9 @@
2
2
  import { cn } from "../lib/cn.mjs";
3
3
  import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
4
4
  import { Label } from "./label.mjs";
5
- import * as React from "react";
6
5
  import { Info } from "lucide-react";
7
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
+ import * as React from "react";
8
8
  //#region src/components/textarea.tsx
9
9
  function Textarea({ className, id, label, required, hint, error, tooltip, ...props }) {
10
10
  const generatedId = React.useId();
@@ -1,6 +1,6 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
3
2
  import { VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
4
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
5
5
  import * as _$class_variance_authority_types0 from "class-variance-authority/types";
6
6
 
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.mjs";
3
- import * as React from "react";
4
3
  import { jsx } from "react/jsx-runtime";
5
4
  import { cva } from "class-variance-authority";
5
+ import * as React from "react";
6
6
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
7
7
  //#region src/components/toggle-group.tsx
8
8
  const toggleGroupItemVariants = cva([
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { Button } from "./button.mjs";
3
3
  import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
4
- import { forwardRef } from "react";
5
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { forwardRef } from "react";
6
6
  import { Slottable } from "@radix-ui/react-slot";
7
7
  //#region src/components/tooltip-icon-button.tsx
8
8
  const TooltipIconButton = forwardRef(({ children, tooltip, side = "bottom", ...rest }, ref) => {
@@ -1,5 +1,5 @@
1
- import * as React from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as React from "react";
3
3
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4
4
 
5
5
  //#region src/components/tooltip.d.ts
@@ -1,5 +1,5 @@
1
- import { ReactNode } from "react";
2
1
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { ReactNode } from "react";
3
3
 
4
4
  //#region src/components/typography.d.ts
5
5
  declare function H1({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "0.0.0-dev.f8c735b",
3
+ "version": "0.0.0-dev.f936359",
4
4
  "description": "Alpic design system — shared UI components",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,12 +23,12 @@
23
23
  "src"
24
24
  ],
25
25
  "peerDependencies": {
26
- "lucide-react": "^1.8.0",
26
+ "lucide-react": "^1.14.0",
27
27
  "react": "^19.2.5",
28
28
  "react-dom": "^19.2.5",
29
- "react-hook-form": "^7.72.1",
29
+ "react-hook-form": "^7.74.0",
30
30
  "sonner": "^2.0.7",
31
- "tailwindcss": "^4.2.2",
31
+ "tailwindcss": "^4.2.4",
32
32
  "tw-animate-css": "^1.4.0"
33
33
  },
34
34
  "dependencies": {
@@ -56,17 +56,17 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@ladle/react": "^5.1.1",
59
- "@tailwindcss/postcss": "^4.2.2",
59
+ "@tailwindcss/postcss": "^4.2.4",
60
60
  "@types/react": "19.2.14",
61
61
  "@types/react-dom": "19.2.3",
62
- "lucide-react": "^1.8.0",
63
- "react-hook-form": "^7.72.1",
62
+ "lucide-react": "^1.14.0",
63
+ "react-hook-form": "^7.74.0",
64
64
  "shx": "^0.4.0",
65
65
  "sonner": "^2.0.7",
66
- "tailwindcss": "^4.2.2",
67
- "tsdown": "^0.21.7",
66
+ "tailwindcss": "^4.2.4",
67
+ "tsdown": "^0.21.10",
68
68
  "tw-animate-css": "^1.4.0",
69
- "typescript": "^6.0.2"
69
+ "typescript": "^6.0.3"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "shx rm -rf dist && tsdown",
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { Slot } from "@radix-ui/react-slot";
3
+ import { Slot, Slottable } from "@radix-ui/react-slot";
4
4
  import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { Loader2 } from "lucide-react";
6
6
  import type * as React from "react";
@@ -46,6 +46,13 @@ const buttonVariants = cva(
46
46
  "bg-destructive text-destructive-foreground",
47
47
  "[@media(hover:hover)]:hover:bg-destructive-hover",
48
48
  ].join(" "),
49
+ cta: [
50
+ "button-cta",
51
+ "h-9 px-4 gap-2 rounded-md",
52
+ "dark:bg-inverted text-foreground",
53
+ "transition-[transform,filter] duration-300 ease-out",
54
+ "active:scale-[0.99]",
55
+ ].join(" "),
49
56
  },
50
57
  size: {
51
58
  default: "type-text-sm",
@@ -65,6 +72,7 @@ interface ButtonProps extends React.ComponentProps<"button">, VariantProps<typeo
65
72
  asChild?: boolean;
66
73
  loading?: boolean;
67
74
  icon?: React.ReactNode;
75
+ iconTrailing?: React.ReactNode;
68
76
  }
69
77
 
70
78
  function Button({
@@ -75,6 +83,7 @@ function Button({
75
83
  asChild = false,
76
84
  loading = false,
77
85
  icon,
86
+ iconTrailing,
78
87
  disabled,
79
88
  children,
80
89
  ...props
@@ -90,14 +99,9 @@ function Button({
90
99
  aria-busy={loading || undefined}
91
100
  {...props}
92
101
  >
93
- {asChild ? (
94
- children
95
- ) : (
96
- <>
97
- {loading ? <Loader2 className="motion-safe:animate-spin" /> : icon}
98
- {children}
99
- </>
100
- )}
102
+ {loading ? <Loader2 className="motion-safe:animate-spin" /> : icon}
103
+ {asChild ? <Slottable>{children}</Slottable> : children}
104
+ {!loading && iconTrailing ? <span data-cta-icon-trailing>{iconTrailing}</span> : null}
101
105
  </Comp>
102
106
  );
103
107
  }
@@ -84,7 +84,9 @@ function Combobox(props: ComboboxProps) {
84
84
 
85
85
  const onOpenChange = useCallback(
86
86
  (newOpen: boolean) => {
87
- if (!isOpenControlled) setUncontrolledOpen(newOpen);
87
+ if (!isOpenControlled) {
88
+ setUncontrolledOpen(newOpen);
89
+ }
88
90
  controlledOnOpenChange?.(newOpen);
89
91
  },
90
92
  [isOpenControlled, controlledOnOpenChange],
@@ -105,7 +107,9 @@ function Combobox(props: ComboboxProps) {
105
107
 
106
108
  const isSelected = useCallback(
107
109
  (itemValue: string) => {
108
- if (multiple) return multiValues.includes(itemValue);
110
+ if (multiple) {
111
+ return multiValues.includes(itemValue);
112
+ }
109
113
  return singleValue === itemValue;
110
114
  },
111
115
  [multiple, singleValue, multiValues],
@@ -123,11 +127,15 @@ function Combobox(props: ComboboxProps) {
123
127
  const next = multiValues.includes(itemValue)
124
128
  ? multiValues.filter((val) => val !== itemValue)
125
129
  : [...multiValues, itemValue];
126
- if (!isControlledMulti) setUncontrolledMultiValue(next);
130
+ if (!isControlledMulti) {
131
+ setUncontrolledMultiValue(next);
132
+ }
127
133
  onValueChangeMulti?.(next);
128
134
  // Stay open in multi mode
129
135
  } else {
130
- if (!isControlledSingle) setUncontrolledSingleValue(itemValue);
136
+ if (!isControlledSingle) {
137
+ setUncontrolledSingleValue(itemValue);
138
+ }
131
139
  onValueChangeSingle?.(itemValue);
132
140
  onOpenChange(false);
133
141
  }
@@ -145,9 +153,13 @@ function Combobox(props: ComboboxProps) {
145
153
 
146
154
  const onDeselect = useCallback(
147
155
  (itemValue: string) => {
148
- if (!multiple) return;
156
+ if (!multiple) {
157
+ return;
158
+ }
149
159
  const next = multiValues.filter((val) => val !== itemValue);
150
- if (!isControlledMulti) setUncontrolledMultiValue(next);
160
+ if (!isControlledMulti) {
161
+ setUncontrolledMultiValue(next);
162
+ }
151
163
  onValueChangeMulti?.(next);
152
164
  },
153
165
  [multiple, isControlledMulti, onValueChangeMulti, multiValues],
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import type { ComponentProps } from "react";
4
+
5
+ import { cn } from "../lib/cn";
6
+ import { Button } from "./button";
7
+
8
+ const GITHUB_ICON_PATH =
9
+ "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12";
10
+
11
+ function GitHubIcon() {
12
+ return (
13
+ <svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
14
+ <path d={GITHUB_ICON_PATH} />
15
+ </svg>
16
+ );
17
+ }
18
+
19
+ type GitHubButtonProps = Omit<ComponentProps<typeof Button>, "variant" | "icon">;
20
+
21
+ function GitHubButton({ className, children, ...props }: GitHubButtonProps) {
22
+ return (
23
+ <Button
24
+ {...props}
25
+ icon={<GitHubIcon />}
26
+ className={cn("bg-foreground text-background [@media(hover:hover)]:hover:bg-foreground/90", className)}
27
+ >
28
+ {children}
29
+ </Button>
30
+ );
31
+ }
32
+
33
+ export type { GitHubButtonProps };
34
+ export { GitHubButton };
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { cn } from "../lib/cn";
4
+
5
+ const CABLE_CAR_SVG = (
6
+ <svg
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ viewBox="0 0 120 130"
9
+ width="240"
10
+ height="260"
11
+ aria-hidden="true"
12
+ className="block h-auto w-full"
13
+ >
14
+ <line x1="60" y1="3" x2="60" y2="58" stroke="#333" strokeWidth="4" />
15
+ <circle cx="60" cy="11" r="10" fill="#555" />
16
+ <rect x="5" y="58" width="110" height="64" rx="4" fill="#e90060" />
17
+ <rect x="5" y="58" width="110" height="20" rx="4" fill="#F5F0E8" />
18
+ <rect x="5" y="68" width="110" height="10" fill="#F5F0E8" />
19
+ <rect x="14" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
20
+ <rect x="47" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
21
+ <rect x="80" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
22
+ <rect x="5" y="115" width="110" height="7" rx="3" fill="#9f0042" />
23
+ </svg>
24
+ );
25
+
26
+ interface PageLoaderProps {
27
+ className?: string;
28
+ }
29
+
30
+ function PageLoader({ className }: PageLoaderProps) {
31
+ return (
32
+ <div
33
+ className={cn("flex min-h-screen items-center justify-center bg-background", className)}
34
+ role="status"
35
+ aria-label="Loading Alpic…"
36
+ >
37
+ <div className="relative h-[120px] w-[200px]">
38
+ <div
39
+ className="absolute top-[30px] left-0 h-[3px] w-full rounded-sm bg-[#6c6c77]"
40
+ style={{ transform: "rotate(-15deg)", transformOrigin: "left center" }}
41
+ />
42
+ <div className="absolute top-[33px] -left-[45px] w-[45px] motion-safe:animate-[alpic-ride_4s_linear_infinite]">
43
+ {CABLE_CAR_SVG}
44
+ </div>
45
+ <div
46
+ className="pointer-events-none absolute -top-[40px] -left-[45px] z-10 h-[200px] w-[95px]"
47
+ style={{ background: "linear-gradient(to right, var(--color-background) 47%, transparent)" }}
48
+ />
49
+ <div
50
+ className="pointer-events-none absolute -top-[40px] left-[142px] z-10 h-[200px] w-[95px]"
51
+ style={{ background: "linear-gradient(to right, transparent, var(--color-background) 53%)" }}
52
+ />
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export type { PageLoaderProps };
59
+ export { PageLoader };