@stasho/ds 0.4.1 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stasho/ds",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "stasho design system",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@
24
24
  "exports": {
25
25
  "./alert": "./src/components/alert/alert.tsx",
26
26
  "./button": "./src/components/button/button.tsx",
27
+ "./icon-button": "./src/components/button/icon-button.tsx",
27
28
  "./checkbox": "./src/components/checkbox/checkbox.tsx",
28
29
  "./input": "./src/components/input/input.tsx",
29
30
  "./textarea": "./src/components/textarea/textarea.tsx",
@@ -40,6 +41,7 @@
40
41
  "./combobox": "./src/components/combobox/combobox.tsx",
41
42
  "./slider": "./src/components/slider/slider.tsx",
42
43
  "./radio-group": "./src/components/radio-group/radio-group.tsx",
44
+ "./selectable-card": "./src/components/selectable-card/selectable-card.tsx",
43
45
  "./ui/skeleton": "./src/components/ui/skeleton.tsx",
44
46
  "./multi-select": "./src/components/multi-select/multi-select.tsx",
45
47
  "./pagination": "./src/components/pagination/pagination.tsx",
@@ -101,6 +101,14 @@ const buttonVariants = cva(
101
101
  "dark:hover:bg-white/[0.04] dark:hover:text-white",
102
102
  "dark:disabled:text-white/30 dark:disabled:bg-transparent",
103
103
  ].join(" "),
104
+ link: [
105
+ // chrome-less inline text action: text-primary, underline on hover, no LED, no padding (zeroed per size below)
106
+ "bg-transparent font-normal underline-offset-2 text-primary",
107
+ "hover:underline active:translate-y-0",
108
+ "dark:text-accent",
109
+ "disabled:text-foreground/30 disabled:no-underline disabled:hover:no-underline",
110
+ "dark:disabled:text-white/30",
111
+ ].join(" "),
104
112
  },
105
113
  size: {
106
114
  xs: "py-[6px] px-3 text-[11px] gap-1.5",
@@ -115,6 +123,10 @@ const buttonVariants = cva(
115
123
  { variant: "outline", size: "sm", class: "py-[6px] px-[13px]" },
116
124
  { variant: "outline", size: "md", class: "py-[8px] px-[17px]" },
117
125
  { variant: "outline", size: "lg", class: "py-[12px] px-[23px]" },
126
+ { variant: "link", size: "xs", class: "p-0" },
127
+ { variant: "link", size: "sm", class: "p-0" },
128
+ { variant: "link", size: "md", class: "p-0" },
129
+ { variant: "link", size: "lg", class: "p-0" },
118
130
  ],
119
131
  defaultVariants: {
120
132
  variant: "primary",
@@ -152,6 +164,8 @@ const ledColorClass: Record<Variant, string> = {
152
164
  outline: "bg-primary/35 text-primary dark:bg-accent/50 dark:text-accent",
153
165
  // ghost: LED is never rendered for ghost, so this entry is a sentinel.
154
166
  ghost: "",
167
+ // link: LED is never rendered for link, so this entry is a sentinel.
168
+ link: "",
155
169
  };
156
170
 
157
171
  // iconLeft glow treatment per variant — applied to the wrapper span.
@@ -163,6 +177,7 @@ const iconGlowClass: Record<Variant, string> = {
163
177
  success: "text-success-foreground",
164
178
  outline: "text-primary dark:text-accent [filter:drop-shadow(0_0_4px_currentColor)]",
165
179
  ghost: "text-foreground/60 dark:text-white/60",
180
+ link: "text-primary dark:text-accent",
166
181
  };
167
182
 
168
183
  type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
@@ -199,8 +214,8 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
199
214
  );
200
215
 
201
216
  const leadingSlot = (() => {
202
- // Loading replaces everything in the leading slot (except on ghost).
203
- if (loading && v !== "ghost") {
217
+ // Loading replaces everything in the leading slot (except on ghost/link).
218
+ if (loading && v !== "ghost" && v !== "link") {
204
219
  return (
205
220
  <span
206
221
  data-led-chase
@@ -241,8 +256,8 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
241
256
  </span>
242
257
  );
243
258
  }
244
- // Resting state with static LED (non-ghost variants).
245
- if (v !== "ghost") {
259
+ // Resting state with static LED (non-ghost/link variants).
260
+ if (v !== "ghost" && v !== "link") {
246
261
  return (
247
262
  <span
248
263
  data-led
@@ -0,0 +1,51 @@
1
+ import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@ac/lib/cn";
4
+ import { buttonVariants } from "./button";
5
+
6
+ // Square inset per size. The glyph dimensions are consumer-controlled (e.g. <Bell size={18} />);
7
+ // the button stays square because the padding is symmetric.
8
+ const squareSizeClass = {
9
+ xs: "p-1",
10
+ sm: "p-1.5",
11
+ md: "p-2",
12
+ lg: "p-2.5",
13
+ } as const;
14
+
15
+ type Variant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>;
16
+ type Size = keyof typeof squareSizeClass;
17
+
18
+ // aria-label is required: an icon-only button must carry an accessible name.
19
+ type IconButtonProps = Omit<
20
+ ButtonHTMLAttributes<HTMLButtonElement>,
21
+ "aria-label"
22
+ > & {
23
+ variant?: Variant;
24
+ size?: Size;
25
+ "aria-label": string;
26
+ children: ReactNode;
27
+ };
28
+
29
+ const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
30
+ ({ variant = "ghost", size = "md", className, children, ...rest }, ref) => (
31
+ <button
32
+ ref={ref}
33
+ type="button"
34
+ className={cn(
35
+ // reuse Button's color/chassis; tailwind-merge collapses the default
36
+ // rectangular size padding into the square p-* appended after it.
37
+ buttonVariants({ variant }),
38
+ "justify-center shrink-0",
39
+ squareSizeClass[size],
40
+ className,
41
+ )}
42
+ {...rest}
43
+ >
44
+ {children}
45
+ </button>
46
+ ),
47
+ );
48
+
49
+ IconButton.displayName = "IconButton";
50
+
51
+ export { IconButton, type IconButtonProps };
@@ -0,0 +1,106 @@
1
+ import {
2
+ forwardRef,
3
+ type ButtonHTMLAttributes,
4
+ type ComponentPropsWithoutRef,
5
+ type ReactNode,
6
+ } from "react";
7
+ import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
8
+ import { cva, type VariantProps } from "class-variance-authority";
9
+ import { Check } from "@phosphor-icons/react";
10
+ import { cn } from "@ac/lib/cn";
11
+
12
+ const selectableCardVariants = cva(
13
+ [
14
+ "relative rounded-2xl border border-edge bg-surface text-left",
15
+ "transition-[colors,box-shadow]",
16
+ "hover:border-edge-hover",
17
+ "focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2",
18
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:border-edge",
19
+ // selected (data-[state=on]) — only SelectableCard receives this state
20
+ "data-[state=on]:border-accent data-[state=on]:bg-accent/5",
21
+ ].join(" "),
22
+ {
23
+ variants: {
24
+ padding: {
25
+ sm: "p-4",
26
+ md: "p-6",
27
+ lg: "p-8",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ padding: "md",
32
+ },
33
+ },
34
+ );
35
+
36
+ type CardPadding = VariantProps<typeof selectableCardVariants>;
37
+
38
+ type ActionCardProps = ButtonHTMLAttributes<HTMLButtonElement> &
39
+ CardPadding & {
40
+ children: ReactNode;
41
+ };
42
+
43
+ const ActionCard = forwardRef<HTMLButtonElement, ActionCardProps>(
44
+ ({ padding, className, children, type, ...rest }, ref) => (
45
+ <button
46
+ ref={ref}
47
+ type={type ?? "button"}
48
+ className={cn(selectableCardVariants({ padding }), className)}
49
+ {...rest}
50
+ >
51
+ {children}
52
+ </button>
53
+ ),
54
+ );
55
+ ActionCard.displayName = "ActionCard";
56
+
57
+ type SelectableCardGroupProps = ComponentPropsWithoutRef<
58
+ typeof ToggleGroupPrimitive.Root
59
+ >;
60
+
61
+ const SelectableCardGroup = forwardRef<
62
+ HTMLDivElement,
63
+ SelectableCardGroupProps
64
+ >(({ className, ...rest }, ref) => (
65
+ <ToggleGroupPrimitive.Root ref={ref} className={className} {...rest} />
66
+ ));
67
+ SelectableCardGroup.displayName = "SelectableCardGroup";
68
+
69
+ type SelectableCardProps = ComponentPropsWithoutRef<
70
+ typeof ToggleGroupPrimitive.Item
71
+ > &
72
+ CardPadding;
73
+
74
+ const SelectableCard = forwardRef<HTMLButtonElement, SelectableCardProps>(
75
+ ({ padding, className, children, ...rest }, ref) => (
76
+ <ToggleGroupPrimitive.Item
77
+ ref={ref}
78
+ className={cn(selectableCardVariants({ padding }), className)}
79
+ {...rest}
80
+ >
81
+ <span
82
+ data-testid="selectable-card-check"
83
+ aria-hidden="true"
84
+ className={cn(
85
+ "absolute right-3 top-3 text-accent",
86
+ "opacity-0 transition-opacity",
87
+ "[[data-state=on]_&]:opacity-100",
88
+ )}
89
+ >
90
+ <Check weight="bold" />
91
+ </span>
92
+ {children}
93
+ </ToggleGroupPrimitive.Item>
94
+ ),
95
+ );
96
+ SelectableCard.displayName = "SelectableCard";
97
+
98
+ export {
99
+ ActionCard,
100
+ SelectableCard,
101
+ SelectableCardGroup,
102
+ selectableCardVariants,
103
+ type ActionCardProps,
104
+ type SelectableCardProps,
105
+ type SelectableCardGroupProps,
106
+ };