@seekalfred/primitives 0.2.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/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/button/button.d.ts +79 -0
- package/dist/button/button.js +120 -0
- package/dist/button/button.js.map +1 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.js +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lib/cn.d.ts +12 -0
- package/dist/lib/cn.js +17 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alfred AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# `@seekalfred/primitives`
|
|
2
|
+
|
|
3
|
+
Accessible, styled primitives built on React Aria Components.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @seekalfred/primitives @seekalfred/styles
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Ours first, yours after. That order is the whole override story.
|
|
11
|
+
import "@seekalfred/styles/base.css";
|
|
12
|
+
import "@seekalfred/primitives/styles.css";
|
|
13
|
+
|
|
14
|
+
import { Button } from "@seekalfred/primitives";
|
|
15
|
+
|
|
16
|
+
<Button variant="primary" size="md" onPress={save}>
|
|
17
|
+
Save changes
|
|
18
|
+
</Button>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
You do not need Tailwind installed. This package ships compiled CSS.
|
|
22
|
+
|
|
23
|
+
## Button
|
|
24
|
+
|
|
25
|
+
| variant | |
|
|
26
|
+
| ------------------- | ------------------------------------------------------ |
|
|
27
|
+
| `primary` (default) | solid brand orange, ink label |
|
|
28
|
+
| `secondary` | solid brand periwinkle, ink label |
|
|
29
|
+
| `outline` | background fill, brand border and label |
|
|
30
|
+
| `ghost` | transparent until hovered |
|
|
31
|
+
| `destructive` | solid `destructive` |
|
|
32
|
+
| `link` | text only, underlines on hover |
|
|
33
|
+
|
|
34
|
+
The primary label is **ink, not white**. Brand orange carries white at only
|
|
35
|
+
2.44:1; ink clears it at 8.35:1, which is the brand system's own rule. For the
|
|
36
|
+
same reason `link` and `outline` use `primary-strong` — a darkened tone of the
|
|
37
|
+
brand hue — because `primary` itself cannot legally be text or a border.
|
|
38
|
+
|
|
39
|
+
Sizes: `sm` (32px) · `md` (36px, default) · `lg` (40px) · `icon` (36px square).
|
|
40
|
+
|
|
41
|
+
`size="icon"` has no visible text, so it **must** carry an `aria-label`. Without
|
|
42
|
+
one it has no accessible name and fails axe's `button-name` rule — the
|
|
43
|
+
`IconOnly` story exists to keep that true.
|
|
44
|
+
|
|
45
|
+
`buttonVariants` is exported. Use it when you need button styling on an element
|
|
46
|
+
this component cannot render:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { buttonVariants } from "@seekalfred/primitives";
|
|
50
|
+
|
|
51
|
+
<a href="/reports" className={buttonVariants({ variant: "outline" })}>
|
|
52
|
+
View reports
|
|
53
|
+
</a>;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Migrating from the legacy shadcn Button
|
|
57
|
+
|
|
58
|
+
The variant and size vocabulary is unchanged, so most call sites move by
|
|
59
|
+
changing the import alone. Five things differ, because this Button is built on
|
|
60
|
+
React Aria Components rather than a bare `<button>`:
|
|
61
|
+
|
|
62
|
+
| legacy | Alfred | |
|
|
63
|
+
| -------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `onClick` | `onPress` | React Aria omits `onClick` outright. `onPress` also covers touch and keyboard, and does not fire on right-click. |
|
|
65
|
+
| `disabled` | `isDisabled` | |
|
|
66
|
+
| `variant="default"` | `variant="primary"` | `default` named nothing. |
|
|
67
|
+
| `size="default"` | `size="md"` | Same 36px height. |
|
|
68
|
+
| `<Button asChild><a /></Button>` | `<a className={buttonVariants({ variant })}>` | React Aria's Button never renders an anchor. A real `Link` primitive lands in Phase 1. |
|
|
69
|
+
|
|
70
|
+
The first four are mechanical. `asChild` is not — each site needs a decision
|
|
71
|
+
about whether it wanted a link or a button.
|
|
72
|
+
|
|
73
|
+
Two things you gain: a `data-[pressed]` state the original never had, and
|
|
74
|
+
`isPending`, which blocks activation while staying focusable and announces
|
|
75
|
+
itself to screen readers.
|
|
76
|
+
|
|
77
|
+
Two things that changed on purpose:
|
|
78
|
+
|
|
79
|
+
- **Focus is an `outline`, not a `ring`.** The original used a 3px box-shadow.
|
|
80
|
+
Box-shadows are discarded under Windows High Contrast, taking the only focus
|
|
81
|
+
indication with them, and axe has no rule that catches it.
|
|
82
|
+
- **No `dark:` variants.** Theming happens in the tokens, under
|
|
83
|
+
`[data-theme="dark"]`. Every `dark:` class in the original is gone, not
|
|
84
|
+
reimplemented.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { VariantProps } from "class-variance-authority";
|
|
2
|
+
import { ButtonProps } from "react-aria-components";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
//#region src/button/button.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Variant classes, exported so a consumer can reuse the exact classes we ship
|
|
7
|
+
* rather than guessing at them. This is also the supported answer for the
|
|
8
|
+
* link-shaped cases React Aria's Button cannot cover: it never renders an
|
|
9
|
+
* anchor, so `<a className={buttonVariants({ variant: "outline" })}>` is how a
|
|
10
|
+
* real navigation target gets button styling until the Link primitive lands.
|
|
11
|
+
*
|
|
12
|
+
* Ported from the legacy shadcn Button. The variant and size vocabulary is
|
|
13
|
+
* deliberately identical so existing call sites migrate by rename, with six
|
|
14
|
+
* corrections that the Alfred contracts require -- each marked below.
|
|
15
|
+
*
|
|
16
|
+
* Every string here is a STATIC literal. Tailwind scans source text, so a class
|
|
17
|
+
* assembled at runtime is invisible to it and would simply never be emitted.
|
|
18
|
+
*
|
|
19
|
+
* State comes from React Aria data attributes rather than pseudo-classes:
|
|
20
|
+
* data-[focus-visible] is keyboard focus only, and data-[pressed] covers mouse,
|
|
21
|
+
* touch and keyboard uniformly. The original had no pressed state at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare const buttonVariants: (props?: ({
|
|
24
|
+
variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive" | "link" | null | undefined;
|
|
25
|
+
size?: "sm" | "md" | "lg" | "icon" | null | undefined;
|
|
26
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Props.
|
|
29
|
+
*
|
|
30
|
+
* CORRECTION 6 -- the legacy Button was a plain `<button>`, so it took
|
|
31
|
+
* `onClick`, `disabled` and Radix's `asChild`. React Aria's Button has none of
|
|
32
|
+
* those: `ButtonProps` explicitly omits `onClick` and `href`. The replacements
|
|
33
|
+
* are `onPress`, `isDisabled`, and `buttonVariants()` on the consumer's own
|
|
34
|
+
* element. See the migration table in the package README.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately NOT intersected with ComponentPropsWithoutRef<"button">: that
|
|
37
|
+
* would advertise disabled, tabIndex and role, which React Aria does not
|
|
38
|
+
* implement and silently ignores. React Aria already forwards the native event
|
|
39
|
+
* surface, so nothing is lost.
|
|
40
|
+
*/
|
|
41
|
+
interface ButtonProps$1 extends Omit<ButtonProps, "className" | "style">, VariantProps<typeof buttonVariants> {
|
|
42
|
+
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
43
|
+
className?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Icon rendered before the label.
|
|
46
|
+
*
|
|
47
|
+
* Mark the glyph `aria-hidden` -- the label is the accessible name, and a
|
|
48
|
+
* second name source only makes the button announce twice. It is sized by the
|
|
49
|
+
* base `[&_svg]` rule at `--icon-sm` unless it carries a `size-*` class of
|
|
50
|
+
* its own, and spaced by `--density-control-gap`.
|
|
51
|
+
*
|
|
52
|
+
* Two named slots rather than one `icon` plus a position prop: a button can
|
|
53
|
+
* legitimately carry a leading glyph AND a trailing affordance, which one
|
|
54
|
+
* icon and a position cannot express.
|
|
55
|
+
*
|
|
56
|
+
* NOT the route for `size="icon"`. An icon-only button passes its glyph as
|
|
57
|
+
* `children`, which is what gives it its single centred child.
|
|
58
|
+
*/
|
|
59
|
+
startIcon?: ReactNode;
|
|
60
|
+
/** Icon rendered after the label. Same rules as {@link ButtonProps.startIcon}. */
|
|
61
|
+
endIcon?: ReactNode;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A button.
|
|
65
|
+
*
|
|
66
|
+
* `size="icon"` renders a square button with no visible text, so it **must**
|
|
67
|
+
* carry an `aria-label` -- otherwise it has no accessible name and fails axe's
|
|
68
|
+
* `button-name` rule. The IconOnly story is the guard on that.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* <Button onPress={save}>Save changes</Button>
|
|
72
|
+
* <Button startIcon={<SaveIcon aria-hidden />}>Save changes</Button>
|
|
73
|
+
* <Button endIcon={<ChevronIcon aria-hidden />}>Continue</Button>
|
|
74
|
+
* <Button variant="outline" size="icon" aria-label="Close"><XIcon /></Button>
|
|
75
|
+
*/
|
|
76
|
+
declare function Button$1({ className, variant, size, startIcon, endIcon, children, ...props }: ButtonProps$1): import("react").JSX.Element;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { Button$1 as Button, ButtonProps$1 as ButtonProps };
|
|
79
|
+
//# sourceMappingURL=button.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { Button } from "react-aria-components";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/button/button.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Variant classes, exported so a consumer can reuse the exact classes we ship
|
|
9
|
+
* rather than guessing at them. This is also the supported answer for the
|
|
10
|
+
* link-shaped cases React Aria's Button cannot cover: it never renders an
|
|
11
|
+
* anchor, so `<a className={buttonVariants({ variant: "outline" })}>` is how a
|
|
12
|
+
* real navigation target gets button styling until the Link primitive lands.
|
|
13
|
+
*
|
|
14
|
+
* Ported from the legacy shadcn Button. The variant and size vocabulary is
|
|
15
|
+
* deliberately identical so existing call sites migrate by rename, with six
|
|
16
|
+
* corrections that the Alfred contracts require -- each marked below.
|
|
17
|
+
*
|
|
18
|
+
* Every string here is a STATIC literal. Tailwind scans source text, so a class
|
|
19
|
+
* assembled at runtime is invisible to it and would simply never be emitted.
|
|
20
|
+
*
|
|
21
|
+
* State comes from React Aria data attributes rather than pseudo-classes:
|
|
22
|
+
* data-[focus-visible] is keyboard focus only, and data-[pressed] covers mouse,
|
|
23
|
+
* touch and keyboard uniformly. The original had no pressed state at all.
|
|
24
|
+
*/
|
|
25
|
+
const buttonVariants = cva([
|
|
26
|
+
"inline-flex items-center justify-center gap-(--density-control-gap) shrink-0 whitespace-nowrap",
|
|
27
|
+
"rounded-md font-sans font-medium",
|
|
28
|
+
"cursor-pointer select-none",
|
|
29
|
+
"border-(length:--border-width) border-transparent bg-transparent",
|
|
30
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
31
|
+
"[&_svg:not([class*='size-'])]:size-(--icon-sm)",
|
|
32
|
+
"outline-none",
|
|
33
|
+
"data-[focus-visible]:outline-(length:--focus-ring-width)",
|
|
34
|
+
"data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
35
|
+
"data-[focus-visible]:outline-(--color-ring)",
|
|
36
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)",
|
|
37
|
+
"data-[pending]:cursor-progress"
|
|
38
|
+
], {
|
|
39
|
+
variants: {
|
|
40
|
+
variant: {
|
|
41
|
+
primary: [
|
|
42
|
+
"bg-primary text-primary-foreground shadow-surface",
|
|
43
|
+
"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard",
|
|
44
|
+
"data-[hovered]:bg-primary-hover",
|
|
45
|
+
"data-[pressed]:bg-primary-pressed"
|
|
46
|
+
],
|
|
47
|
+
secondary: [
|
|
48
|
+
"bg-secondary text-secondary-foreground shadow-surface",
|
|
49
|
+
"motion-safe:transition-colors",
|
|
50
|
+
"data-[hovered]:bg-secondary-hover",
|
|
51
|
+
"data-[pressed]:bg-secondary-pressed"
|
|
52
|
+
],
|
|
53
|
+
outline: [
|
|
54
|
+
"bg-background text-primary-strong border-(length:--border-width) border-primary-strong shadow-surface",
|
|
55
|
+
"motion-safe:transition-colors",
|
|
56
|
+
"data-[hovered]:bg-muted",
|
|
57
|
+
"data-[pressed]:bg-muted data-[pressed]:border-foreground"
|
|
58
|
+
],
|
|
59
|
+
ghost: [
|
|
60
|
+
"bg-transparent text-primary-strong",
|
|
61
|
+
"motion-safe:transition-colors",
|
|
62
|
+
"data-[hovered]:bg-muted",
|
|
63
|
+
"data-[pressed]:bg-muted data-[pressed]:border-primary-strong"
|
|
64
|
+
],
|
|
65
|
+
destructive: [
|
|
66
|
+
"bg-destructive text-destructive-foreground shadow-surface",
|
|
67
|
+
"motion-safe:transition-colors",
|
|
68
|
+
"data-[hovered]:bg-destructive-hover",
|
|
69
|
+
"data-[pressed]:bg-destructive-pressed"
|
|
70
|
+
],
|
|
71
|
+
link: [
|
|
72
|
+
"bg-transparent text-primary-strong underline-offset-(--underline-offset)",
|
|
73
|
+
"data-[hovered]:underline",
|
|
74
|
+
"data-[pressed]:underline"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
size: {
|
|
78
|
+
sm: "h-(--density-control-h-sm) px-(--density-control-pad-sm) text-sm",
|
|
79
|
+
md: "h-(--density-control-h-md) px-(--density-control-pad-md) text-base",
|
|
80
|
+
lg: "h-(--density-control-h-lg) px-(--density-control-pad-lg) text-lg",
|
|
81
|
+
icon: "size-(--density-control-h-md) px-0 text-base"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
defaultVariants: {
|
|
85
|
+
variant: "primary",
|
|
86
|
+
size: "md"
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* A button.
|
|
91
|
+
*
|
|
92
|
+
* `size="icon"` renders a square button with no visible text, so it **must**
|
|
93
|
+
* carry an `aria-label` -- otherwise it has no accessible name and fails axe's
|
|
94
|
+
* `button-name` rule. The IconOnly story is the guard on that.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* <Button onPress={save}>Save changes</Button>
|
|
98
|
+
* <Button startIcon={<SaveIcon aria-hidden />}>Save changes</Button>
|
|
99
|
+
* <Button endIcon={<ChevronIcon aria-hidden />}>Continue</Button>
|
|
100
|
+
* <Button variant="outline" size="icon" aria-label="Close"><XIcon /></Button>
|
|
101
|
+
*/
|
|
102
|
+
function Button$1({ className, variant, size, startIcon, endIcon, children, ...props }) {
|
|
103
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
104
|
+
...props,
|
|
105
|
+
"data-slot": "button",
|
|
106
|
+
className: cn(buttonVariants({
|
|
107
|
+
variant,
|
|
108
|
+
size
|
|
109
|
+
}), className),
|
|
110
|
+
children: startIcon === void 0 && endIcon === void 0 ? children : (renderProps) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
111
|
+
startIcon,
|
|
112
|
+
typeof children === "function" ? children(renderProps) : children,
|
|
113
|
+
endIcon
|
|
114
|
+
] })
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { Button$1 as Button, buttonVariants };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button.js","names":["Button","AriaButton"],"sources":["../../src/button/button.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport { Button as AriaButton, type ButtonProps as AriaButtonProps } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Variant classes, exported so a consumer can reuse the exact classes we ship\n * rather than guessing at them. This is also the supported answer for the\n * link-shaped cases React Aria's Button cannot cover: it never renders an\n * anchor, so `<a className={buttonVariants({ variant: \"outline\" })}>` is how a\n * real navigation target gets button styling until the Link primitive lands.\n *\n * Ported from the legacy shadcn Button. The variant and size vocabulary is\n * deliberately identical so existing call sites migrate by rename, with six\n * corrections that the Alfred contracts require -- each marked below.\n *\n * Every string here is a STATIC literal. Tailwind scans source text, so a class\n * assembled at runtime is invisible to it and would simply never be emitted.\n *\n * State comes from React Aria data attributes rather than pseudo-classes:\n * data-[focus-visible] is keyboard focus only, and data-[pressed] covers mouse,\n * touch and keyboard uniformly. The original had no pressed state at all.\n */\nexport const buttonVariants = cva(\n [\n \"inline-flex items-center justify-center gap-(--density-control-gap) shrink-0 whitespace-nowrap\",\n // No leading-* here on purpose. tailwind-merge treats a later text-* class\n // as owning line-height (a Tailwind v3 assumption, where text-base carried\n // one), so a leading-* in the base string is silently STRIPPED by cn() once\n // a size variant adds text-base. It is not needed either: the control has a\n // fixed height and centres with flex, so line-height cannot overflow it.\n // A component with auto height must put leading-* AFTER its text-* class.\n \"rounded-md font-sans font-medium\",\n \"cursor-pointer select-none\",\n\n // font-sans above is part of the same problem: a <button> does not inherit\n // the page font, so without a reset it renders in the user-agent face\n // (Arial in Chromium) rather than the design system's. The same applies to\n // bg-transparent: an unstyled <button> paints the user-agent ButtonFace\n // grey -- which is what surfaces the moment a fill class fails to resolve,\n // and is why the paint stories compare against tokens rather than checking\n // for transparency.\n //\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may never load\n // one. A bare <button> then keeps its user-agent border (2px, black, in\n // Chromium) and inline padding, which is a black box around every variant\n // that does not set a border of its own. The original inherited a zeroed\n // border from Tailwind Preflight in the app; a library component cannot\n // assume the consumer supplied one.\n \"border-(length:--border-width) border-transparent bg-transparent\",\n\n // Icon handling, ported verbatim: an svg child is sized unless the caller\n // already said otherwise, and never swallows the press.\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n\n // CORRECTION 1 -- the original focused with `ring-[3px]`, which is a\n // box-shadow. Box-shadows are discarded under forced-colors, taking the\n // only focus indication with them, and axe has no rule that would catch it\n // (ARCHITECTURE section 5, rule 6). A real outline survives.\n \"outline-none\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n\n // CORRECTION 2 -- `disabled:pointer-events-none` also kills the cursor, so\n // the control reads as inert rather than as unavailable. React Aria already\n // blocks activation; this only has to say so.\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n\n // React Aria's own pending state. The original had no equivalent.\n \"data-[pending]:cursor-progress\",\n ],\n {\n variants: {\n variant: {\n // CORRECTION 3 -- the legacy button filled this variant with the brand\n // gradient. It is flat here, because the brand system caps\n // --gradient-brand at one intentional element per view and keeps that\n // budget for the logo and progress bars: \"a gradient on every button\n // spends that budget everywhere. Ours is the flat primary.\"\n //\n // CORRECTION 4 -- the label is ink, not white. Brand orange #FF8431\n // carries white at 2.44:1; ink clears it at 8.35:1, which is the brand\n // system's own --text-on-orange rule. Its Button ships white only as a\n // dated, individually signed-off exception, and this library's contrast\n // contract is build-failing with no exemption mechanism to inherit it.\n //\n // CORRECTION 7 -- hover and pressed are their own TOKENS, not opacity\n // modifiers on the fill. `bg-primary/90` lightened the button toward the\n // page, which reads as receding rather than interactive and was nearly\n // invisible; it also made the label pair unmeasurable, because an alpha\n // composite depends on whatever happens to sit behind it. The brand system\n // steps DOWN its ramp instead -- 500 rest, 600 hover, 700 pressed -- and\n // every one of those fills is now in the contrast contract.\n primary: [\n \"bg-primary text-primary-foreground shadow-surface\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"data-[hovered]:bg-primary-hover\",\n \"data-[pressed]:bg-primary-pressed\",\n ],\n secondary: [\n \"bg-secondary text-secondary-foreground shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-secondary-hover\",\n \"data-[pressed]:bg-secondary-pressed\",\n ],\n // primary-strong, never primary: the brand orange is 2.44:1 on the page\n // and cannot legally be a label OR a border. That is the whole reason\n // the token exists.\n outline: [\n \"bg-background text-primary-strong border-(length:--border-width) border-primary-strong shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n \"data-[pressed]:bg-muted data-[pressed]:border-foreground\",\n ],\n ghost: [\n \"bg-transparent text-primary-strong\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n // CORRECTION 5 -- pressed must not be background-only. A ghost button\n // has no border at rest, so the pressed border is the non-colour cue\n // that survives forced-colors (rule 6 again).\n \"data-[pressed]:bg-muted data-[pressed]:border-primary-strong\",\n ],\n destructive: [\n \"bg-destructive text-destructive-foreground shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-destructive-hover\",\n \"data-[pressed]:bg-destructive-pressed\",\n ],\n link: [\n \"bg-transparent text-primary-strong underline-offset-(--underline-offset)\",\n \"data-[hovered]:underline\",\n \"data-[pressed]:underline\",\n ],\n },\n size: {\n // Height, padding and label size all come from the control scale, so\n // data-density moves them together. The icon-only variant is square at\n // the md height. The legacy shadcn rule that tightened padding when a\n // child was an icon is gone: it needed three more magic numbers, the\n // brand system has no equivalent, and the gap token now owns icon\n // spacing.\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm) text-sm\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md) text-base\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg) text-lg\",\n // text-base even though there is no visible label: without it the\n // user-agent default (13.33px in Chromium) leaks in and sizes any\n // em-based child against the wrong root.\n icon: \"size-(--density-control-h-md) px-0 text-base\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\n/**\n * Props.\n *\n * CORRECTION 6 -- the legacy Button was a plain `<button>`, so it took\n * `onClick`, `disabled` and Radix's `asChild`. React Aria's Button has none of\n * those: `ButtonProps` explicitly omits `onClick` and `href`. The replacements\n * are `onPress`, `isDisabled`, and `buttonVariants()` on the consumer's own\n * element. See the migration table in the package README.\n *\n * Deliberately NOT intersected with ComponentPropsWithoutRef<\"button\">: that\n * would advertise disabled, tabIndex and role, which React Aria does not\n * implement and silently ignores. React Aria already forwards the native event\n * surface, so nothing is lost.\n */\nexport interface ButtonProps\n extends Omit<AriaButtonProps, \"className\" | \"style\">, VariantProps<typeof buttonVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Icon rendered before the label.\n *\n * Mark the glyph `aria-hidden` -- the label is the accessible name, and a\n * second name source only makes the button announce twice. It is sized by the\n * base `[&_svg]` rule at `--icon-sm` unless it carries a `size-*` class of\n * its own, and spaced by `--density-control-gap`.\n *\n * Two named slots rather than one `icon` plus a position prop: a button can\n * legitimately carry a leading glyph AND a trailing affordance, which one\n * icon and a position cannot express.\n *\n * NOT the route for `size=\"icon\"`. An icon-only button passes its glyph as\n * `children`, which is what gives it its single centred child.\n */\n startIcon?: ReactNode;\n /** Icon rendered after the label. Same rules as {@link ButtonProps.startIcon}. */\n endIcon?: ReactNode;\n}\n\n/**\n * A button.\n *\n * `size=\"icon\"` renders a square button with no visible text, so it **must**\n * carry an `aria-label` -- otherwise it has no accessible name and fails axe's\n * `button-name` rule. The IconOnly story is the guard on that.\n *\n * @example\n * <Button onPress={save}>Save changes</Button>\n * <Button startIcon={<SaveIcon aria-hidden />}>Save changes</Button>\n * <Button endIcon={<ChevronIcon aria-hidden />}>Continue</Button>\n * <Button variant=\"outline\" size=\"icon\" aria-label=\"Close\"><XIcon /></Button>\n */\nexport function Button({\n className,\n variant,\n size,\n startIcon,\n endIcon,\n children,\n ...props\n}: ButtonProps) {\n return (\n <AriaButton\n {...props}\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size }), className)}\n >\n {/*\n * children passes through UNTOUCHED when neither slot is used, so a\n * button with no icon renders byte-identical DOM to before this prop\n * existed. That is what keeps this an additive change rather than a\n * structural one -- and the childOrder assertions in the stories are what\n * hold it true, since they read the button's direct children.\n *\n * The slot branch has to be a render function because React Aria's\n * children are ChildrenOrFunction<ButtonRenderProps>: a consumer may pass\n * a function to read isPressed or isPending, and wrapping the node\n * directly would silently render that function as nothing. The fragment\n * adds no element of its own.\n */}\n {startIcon === undefined && endIcon === undefined\n ? children\n : (renderProps) => (\n <>\n {startIcon}\n {typeof children === \"function\" ? children(renderProps) : children}\n {endIcon}\n </>\n )}\n </AriaButton>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB,IAC5B;CACE;CAOA;CACA;CAgBA;CAIA;CACA;CAMA;CACA;CACA;CACA;CAKA;CAGA;AACF,GACA;CACE,UAAU;EACR,SAAS;GAoBP,SAAS;IACP;IACA;IACA;IACA;GACF;GACA,WAAW;IACT;IACA;IACA;IACA;GACF;GAIA,SAAS;IACP;IACA;IACA;IACA;GACF;GACA,OAAO;IACL;IACA;IACA;IAIA;GACF;GACA,aAAa;IACX;IACA;IACA;IACA;GACF;GACA,MAAM;IACJ;IACA;IACA;GACF;EACF;EACA,MAAM;GAOJ,IAAI;GACJ,IAAI;GACJ,IAAI;GAIJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;;;;;;;;;;;;;;AAqDA,SAAgBA,SAAO,EACrB,WACA,SACA,MACA,WACA,SACA,UACA,GAAG,SACW;CACd,OACE,oBAACC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,eAAe;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAezD,UAAA,cAAc,KAAA,KAAa,YAAY,KAAA,IACpC,YACC,gBACC,qBAAA,UAAA,EAAA,UAAA;GACG;GACA,OAAO,aAAa,aAAa,SAAS,WAAW,IAAI;GACzD;EACD,EAAA,CAAA;CAEE,CAAA;AAEhB"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/lib/cn.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ClassValue } from "clsx";
|
|
2
|
+
//#region src/lib/cn.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Merge class names, with the caller's classes winning conflicts.
|
|
5
|
+
*
|
|
6
|
+
* Every component runs its own classes through this with the consumer's
|
|
7
|
+
* className last, so a consumer class replaces ours inside the class
|
|
8
|
+
* attribute rather than fighting it on specificity.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cn(...inputs: ClassValue[]): string;
|
|
11
|
+
//#endregion
|
|
12
|
+
//# sourceMappingURL=cn.d.ts.map
|
package/dist/lib/cn.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
//#region src/lib/cn.ts
|
|
4
|
+
/**
|
|
5
|
+
* Merge class names, with the caller's classes winning conflicts.
|
|
6
|
+
*
|
|
7
|
+
* Every component runs its own classes through this with the consumer's
|
|
8
|
+
* className last, so a consumer class replaces ours inside the class
|
|
9
|
+
* attribute rather than fighting it on specificity.
|
|
10
|
+
*/
|
|
11
|
+
function cn(...inputs) {
|
|
12
|
+
return twMerge(clsx(inputs));
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { cn };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=cn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cn.js","names":[],"sources":["../../src/lib/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names, with the caller's classes winning conflicts.\n *\n * Every component runs its own classes through this with the consumer's\n * className last, so a consumer class replaces ours inside the class\n * attribute rather than fighting it on specificity.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,GAAG,GAAG,QAA8B;CAClD,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-duration:initial;--tw-ease:initial}}}@layer alfred.reset,alfred.tokens,alfred.components.primitives,alfred.components.patterns,alfred.components.charts,alfred.components.ai;@layer alfred.utilities.primitives{.invisible{visibility:hidden}.visible{visibility:visible}.fixed{position:fixed}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-\(--density-control-h-md\){width:var(--density-control-h-md);height:var(--density-control-h-md)}.h-\(--density-control-h-lg\){height:var(--density-control-h-lg)}.h-\(--density-control-h-md\){height:var(--density-control-h-md)}.h-\(--density-control-h-sm\){height:var(--density-control-h-sm)}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.items-center{align-items:center}.justify-center{justify-content:center}.gap-\(--density-control-gap\){gap:var(--density-control-gap)}.rounded-md{border-radius:var(--radius-md,.75rem)}.border{border-style:var(--tw-border-style);border-width:1px}.border-\(length\:--border-width\){border-style:var(--tw-border-style);border-width:var(--border-width)}.border-primary-strong{border-color:var(--color-primary-strong,var(--alfred-orange-750))}.border-transparent{border-color:#0000}.bg-background{background-color:var(--color-background,var(--alfred-white))}.bg-destructive{background-color:var(--color-destructive,var(--alfred-danger-600))}.bg-primary,.bg-primary\/90{background-color:var(--color-primary,var(--alfred-orange-500))}@supports (color:color-mix(in lab, red, red)){.bg-primary\/90{background-color:color-mix(in oklab, var(--color-primary,var(--alfred-orange-500)) 90%, transparent)}}.bg-secondary{background-color:var(--color-secondary,var(--alfred-periwinkle-400))}.bg-transparent{background-color:#0000}.px-\(--density-control-pad-lg\){padding-inline:var(--density-control-pad-lg)}.px-\(--density-control-pad-md\){padding-inline:var(--density-control-pad-md)}.px-\(--density-control-pad-sm\){padding-inline:var(--density-control-pad-sm)}.px-0{padding-inline:0}.font-sans{font-family:var(--font-sans,"Satoshi", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif)}.text-base{font-size:var(--text-base,1rem)}.text-lg{font-size:var(--text-lg,1.125rem)}.text-sm{font-size:var(--text-sm,.875rem)}.font-medium{--tw-font-weight:var(--font-weight-medium,500);font-weight:var(--font-weight-medium,500)}.whitespace-nowrap{white-space:nowrap}.text-destructive-foreground{color:var(--color-destructive-foreground,var(--alfred-white))}.text-primary-foreground{color:var(--color-primary-foreground,var(--alfred-ink-900))}.text-primary-strong{color:var(--color-primary-strong,var(--alfred-orange-750))}.text-secondary-foreground{color:var(--color-secondary-foreground,var(--alfred-ink-900))}.underline-offset-\(--underline-offset\){text-underline-offset:var(--underline-offset)}.shadow-surface{--tw-shadow:0 1px 2px var(--tw-shadow-color,#02021e0d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-\[3px\]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.data-\[disabled\]\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.data-\[disabled\]\:opacity-\(--opacity-disabled\)[data-disabled]{opacity:var(--opacity-disabled)}.data-\[focus-visible\]\:outline-\(length\:--focus-ring-width\)[data-focus-visible]{outline-style:var(--tw-outline-style);outline-width:var(--focus-ring-width)}.data-\[focus-visible\]\:outline-offset-\(--focus-ring-offset\)[data-focus-visible]{outline-offset:var(--focus-ring-offset)}.data-\[focus-visible\]\:outline-\(--color-ring\)[data-focus-visible]{outline-color:var(--color-ring)}.data-\[hovered\]\:bg-destructive-hover[data-hovered]{background-color:var(--color-destructive-hover,var(--alfred-danger-700))}.data-\[hovered\]\:bg-muted[data-hovered]{background-color:var(--color-muted,var(--alfred-gray-100))}.data-\[hovered\]\:bg-primary-hover[data-hovered]{background-color:var(--color-primary-hover,var(--alfred-orange-600))}.data-\[hovered\]\:bg-secondary-hover[data-hovered]{background-color:var(--color-secondary-hover,var(--alfred-periwinkle-500))}.data-\[hovered\]\:underline[data-hovered]{text-decoration-line:underline}.data-\[pending\]\:cursor-progress[data-pending]{cursor:progress}.data-\[pressed\]\:border-foreground[data-pressed]{border-color:var(--color-foreground,var(--alfred-ink-900))}.data-\[pressed\]\:border-primary-strong[data-pressed]{border-color:var(--color-primary-strong,var(--alfred-orange-750))}.data-\[pressed\]\:bg-destructive-pressed[data-pressed]{background-color:var(--color-destructive-pressed,var(--alfred-danger-800))}.data-\[pressed\]\:bg-muted[data-pressed]{background-color:var(--color-muted,var(--alfred-gray-100))}.data-\[pressed\]\:bg-primary-pressed[data-pressed]{background-color:var(--color-primary-pressed,var(--alfred-orange-700))}.data-\[pressed\]\:bg-secondary-pressed[data-pressed]{background-color:var(--color-secondary-pressed,var(--alfred-periwinkle-600))}.data-\[pressed\]\:underline[data-pressed]{text-decoration-line:underline}@media (prefers-reduced-motion:no-preference){.motion-safe\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.motion-safe\:duration-\(--duration-fast\){--tw-duration:var(--duration-fast);transition-duration:var(--duration-fast)}.motion-safe\:ease-standard{--tw-ease:var(--ease-standard,cubic-bezier(.22, .61, .36, 1));transition-timing-function:var(--ease-standard,cubic-bezier(.22, .61, .36, 1))}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-\(--icon-sm\) svg:not([class*=size-]){width:var(--icon-sm);height:var(--icon-sm)}}@layer alfred.utilities.patterns,alfred.utilities.charts,alfred.utilities.ai;@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@seekalfred/primitives",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Accessible, styled primitives built on React Aria Components.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/e902-ailabs/alfred-component-library.git",
|
|
10
|
+
"directory": "packages/primitives"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": [
|
|
16
|
+
"**/*.css"
|
|
17
|
+
],
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./button": {
|
|
28
|
+
"types": "./dist/button/index.d.ts",
|
|
29
|
+
"default": "./dist/button/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./styles.css": "./dist/styles.css",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": "^19.0.0",
|
|
36
|
+
"react-aria-components": "^1.21.0",
|
|
37
|
+
"react-dom": "^19.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@seekalfred/tokens": "^0.2.0",
|
|
41
|
+
"class-variance-authority": "0.7.1",
|
|
42
|
+
"clsx": "2.1.1",
|
|
43
|
+
"tailwind-merge": "3.6.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
47
|
+
"@seekalfred/styles": "^0.2.0",
|
|
48
|
+
"@seekalfred/tsconfig": "0.0.0",
|
|
49
|
+
"@size-limit/preset-small-lib": "13.0.3",
|
|
50
|
+
"@storybook/react-vite": "10.6.0",
|
|
51
|
+
"@tailwindcss/cli": "4.3.3",
|
|
52
|
+
"@types/react": "19.2.18",
|
|
53
|
+
"@types/react-dom": "19.2.7",
|
|
54
|
+
"publint": "0.3.24",
|
|
55
|
+
"react": "19.2.8",
|
|
56
|
+
"react-aria-components": "1.21.1",
|
|
57
|
+
"react-dom": "19.2.8",
|
|
58
|
+
"size-limit": "13.0.3",
|
|
59
|
+
"storybook": "10.6.0",
|
|
60
|
+
"tailwindcss": "4.3.3",
|
|
61
|
+
"tsdown": "0.23.0",
|
|
62
|
+
"typescript": "6.0.3"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsdown && tailwindcss -i src/styles.css -o dist/styles.css --minify",
|
|
66
|
+
"dev": "tsdown --watch",
|
|
67
|
+
"dev:css": "tailwindcss -i src/styles.css -o dist/styles.css --watch",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
69
|
+
"check:package": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints ./styles.css && size-limit"
|
|
70
|
+
}
|
|
71
|
+
}
|