cortena-ui 0.1.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/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # cortena-ui
2
+
3
+ The shared React component library for cortenaweb and every Cortena extension.
4
+ shadcn components over [Base UI](https://base-ui.com), styled entirely from
5
+ `cortena-design` tokens. Published to npm as **`cortena-ui`**, versioned in step
6
+ with the tokens, from this repo's `packages/ui`.
7
+
8
+ Intent and decisions: `cortena-docs/cortenaUI&Design/02-ui-components.md`.
9
+ Consuming it: `../../CONSUMING.md`, section "cortena-ui".
10
+
11
+ ## Rules for components in this package
12
+
13
+ - **Every visual value is a token.** `pnpm lint:design` runs the same
14
+ `cortena-design-lint` a consumer runs and fails the build on a literal.
15
+ - **Base UI underneath, never Radix.** The rendered element is changed with
16
+ Base UI's `render` prop, not `asChild`. Open state is `data-open`, not
17
+ `data-state`; use the `open:` / `closed:` variants from `styles.css`.
18
+ - **Text utilities carry a type hint.** Tailwind cannot tell whether
19
+ `text-[var(--x)]` is a colour or a size, so write
20
+ `text-[color:var(--ds-foreground)]` and `text-[length:var(--ds-text-caption)]`.
21
+ Backgrounds, borders and radii are unambiguous and need no hint.
22
+ - **Tested in a real browser.** Tests assert computed pixels against the
23
+ token the browser resolved, in light and dark Chromium instances. A dangling
24
+ `var()` paints transparent, which the tests catch and jsdom cannot.
25
+ - **Every component appears in the guide.** `guide/sections.tsx` is the visual
26
+ check in light, dark and system-dark; add an entry with every variant and
27
+ size when adding a component.
28
+
29
+ ## Commands
30
+
31
+ ```bash
32
+ pnpm check # types
33
+ pnpm lint:design # no literals where a token exists; no dangling var()
34
+ pnpm build # dist/ via tsdown (ESM + d.ts); also runs on prepack
35
+ pnpm test # Vitest browser mode, chromium light + dark
36
+ pnpm guide # three-theme guide on a local Vite server
37
+ ```
38
+
39
+ From the repo root the same are `pnpm ui:check`, `ui:build`, `ui:test`, `ui:guide`.
40
+
41
+ ## Layout
42
+
43
+ ```
44
+ src/index.ts public surface; add an export per component
45
+ src/components/*.tsx one file per component, shadcn shape, Base UI primitives
46
+ src/lib/cn.ts clsx + tailwind-merge
47
+ src/styles/index.css what consumers import after the tokens: animation
48
+ utilities and the Base UI data-attribute variants
49
+ test/ browser tests; setup.css is the consumer recipe verbatim
50
+ guide/ Vite app: ?theme=light|dark|system frames, or all three
51
+ ```
52
+
53
+ ## Releasing
54
+
55
+ ```bash
56
+ pnpm ui:check && pnpm ui:test
57
+ cd packages/ui && npm version minor && npm publish # prepack builds dist/
58
+ git push --follow-tags
59
+ ```
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { ClassValue } from "clsx";
3
+ import { Button as Button$1 } from "@base-ui/react/button";
4
+ import { VariantProps } from "class-variance-authority";
5
+ import * as React from "react";
6
+ //#region src/lib/cn.d.ts
7
+ /**
8
+ * Merge class names the shadcn way: clsx for conditionals, tailwind-merge so a
9
+ * consumer's `className` wins over a component's default for the same utility.
10
+ */
11
+ export declare function cn(...inputs: ClassValue[]): string;
12
+ //#endregion
13
+ //#region src/components/button.d.ts
14
+ /**
15
+ * Button.
16
+ *
17
+ * Base UI supplies the behaviour (native button semantics, `focusableWhenDisabled`,
18
+ * keyboard activation on non-button elements through `render`). Every visual
19
+ * value is a cortena-design token; nothing here is a literal the lint would flag.
20
+ *
21
+ * Radix users: `asChild` is gone. Pass `render={<a href=... />}` to change the
22
+ * rendered element, which is Base UI's equivalent.
23
+ */
24
+ declare const buttonVariants: (props?: ({
25
+ variant?: "primary" | "secondary" | "outline" | "ghost" | "soft" | "destructive" | "link" | null | undefined;
26
+ size?: "sm" | "md" | "lg" | "icon" | "icon-sm" | null | undefined;
27
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
28
+ interface ButtonProps extends React.ComponentProps<typeof Button$1>, VariantProps<typeof buttonVariants> {}
29
+ declare function Button({ className, variant, size, ...props }: ButtonProps): React.JSX.Element;
30
+ //#endregion
31
+ export { Button, type ButtonProps, buttonVariants };
32
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ import { Button as Button$1 } from "@base-ui/react/button";
5
+ import { cva } from "class-variance-authority";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/lib/cn.ts
8
+ /**
9
+ * Merge class names the shadcn way: clsx for conditionals, tailwind-merge so a
10
+ * consumer's `className` wins over a component's default for the same utility.
11
+ */
12
+ function cn(...inputs) {
13
+ return twMerge(clsx(inputs));
14
+ }
15
+ //#endregion
16
+ //#region src/components/button.tsx
17
+ /**
18
+ * Button.
19
+ *
20
+ * Base UI supplies the behaviour (native button semantics, `focusableWhenDisabled`,
21
+ * keyboard activation on non-button elements through `render`). Every visual
22
+ * value is a cortena-design token; nothing here is a literal the lint would flag.
23
+ *
24
+ * Radix users: `asChild` is gone. Pass `render={<a href=... />}` to change the
25
+ * rendered element, which is Base UI's equivalent.
26
+ */
27
+ const buttonVariants = cva([
28
+ "inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap",
29
+ "font-medium",
30
+ "rounded-[var(--ds-radius-md)]",
31
+ "border",
32
+ "transition-[background-color,border-color,color,box-shadow,filter]",
33
+ "duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]",
34
+ "outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40",
35
+ "disabled:pointer-events-none disabled:opacity-50",
36
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0"
37
+ ], {
38
+ variants: {
39
+ variant: {
40
+ primary: "border-[var(--ds-primary)] bg-[var(--ds-primary)] text-[color:var(--ds-primary-foreground)] hover:border-[var(--ds-primary-hover)] hover:bg-[var(--ds-primary-hover)]",
41
+ secondary: "border-[var(--ds-border)] bg-[var(--ds-secondary)] text-[color:var(--ds-secondary-foreground)] hover:border-[var(--ds-border-strong)] hover:bg-[var(--ds-hover)]",
42
+ outline: "border-[var(--ds-border-strong)] bg-transparent text-[color:var(--ds-foreground)] hover:bg-[var(--ds-hover)]",
43
+ ghost: "border-transparent bg-transparent text-[color:var(--ds-muted-foreground)] hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]",
44
+ soft: "border-transparent bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary-soft-foreground)] hover:bg-[var(--ds-primary-soft)] hover:brightness-95",
45
+ destructive: "border-[var(--ds-destructive)] bg-[var(--ds-destructive)] text-[color:var(--ds-destructive-foreground)] hover:brightness-110",
46
+ link: "border-transparent bg-transparent text-[color:var(--ds-primary)] underline-offset-4 hover:underline"
47
+ },
48
+ size: {
49
+ sm: "h-8 px-3 text-[length:var(--ds-text-caption)] rounded-[var(--ds-radius-sm)] [&_svg]:size-3.5",
50
+ md: "h-9 px-4 text-[length:var(--ds-text-caption-lg)] [&_svg]:size-4",
51
+ lg: "h-10 px-5 text-[length:var(--ds-text-body-sm)] [&_svg]:size-4",
52
+ icon: "size-9 [&_svg]:size-4",
53
+ "icon-sm": "size-8 rounded-[var(--ds-radius-sm)] [&_svg]:size-3.5"
54
+ }
55
+ },
56
+ defaultVariants: {
57
+ variant: "primary",
58
+ size: "md"
59
+ }
60
+ });
61
+ function Button({ className, variant, size, ...props }) {
62
+ return /* @__PURE__ */ jsx(Button$1, {
63
+ "data-slot": "button",
64
+ className: cn(buttonVariants({
65
+ variant,
66
+ size
67
+ }), className),
68
+ ...props
69
+ });
70
+ }
71
+ //#endregion
72
+ export { Button, buttonVariants, cn };
73
+
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["BaseButton"],"sources":["../src/lib/cn.ts","../src/components/button.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names the shadcn way: clsx for conditionals, tailwind-merge so a\n * consumer's `className` wins over a component's default for the same utility.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { Button as BaseButton } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Button.\n *\n * Base UI supplies the behaviour (native button semantics, `focusableWhenDisabled`,\n * keyboard activation on non-button elements through `render`). Every visual\n * value is a cortena-design token; nothing here is a literal the lint would flag.\n *\n * Radix users: `asChild` is gone. Pass `render={<a href=... />}` to change the\n * rendered element, which is Base UI's equivalent.\n */\nconst buttonVariants = cva(\n [\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap\",\n \"font-medium\",\n \"rounded-[var(--ds-radius-md)]\",\n \"border\",\n \"transition-[background-color,border-color,color,box-shadow,filter]\",\n \"duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n ],\n {\n variants: {\n variant: {\n primary:\n \"border-[var(--ds-primary)] bg-[var(--ds-primary)] text-[color:var(--ds-primary-foreground)] hover:border-[var(--ds-primary-hover)] hover:bg-[var(--ds-primary-hover)]\",\n secondary:\n \"border-[var(--ds-border)] bg-[var(--ds-secondary)] text-[color:var(--ds-secondary-foreground)] hover:border-[var(--ds-border-strong)] hover:bg-[var(--ds-hover)]\",\n outline:\n \"border-[var(--ds-border-strong)] bg-transparent text-[color:var(--ds-foreground)] hover:bg-[var(--ds-hover)]\",\n ghost:\n \"border-transparent bg-transparent text-[color:var(--ds-muted-foreground)] hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]\",\n soft:\n \"border-transparent bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary-soft-foreground)] hover:bg-[var(--ds-primary-soft)] hover:brightness-95\",\n destructive:\n \"border-[var(--ds-destructive)] bg-[var(--ds-destructive)] text-[color:var(--ds-destructive-foreground)] hover:brightness-110\",\n link: \"border-transparent bg-transparent text-[color:var(--ds-primary)] underline-offset-4 hover:underline\",\n },\n size: {\n sm: \"h-8 px-3 text-[length:var(--ds-text-caption)] rounded-[var(--ds-radius-sm)] [&_svg]:size-3.5\",\n md: \"h-9 px-4 text-[length:var(--ds-text-caption-lg)] [&_svg]:size-4\",\n lg: \"h-10 px-5 text-[length:var(--ds-text-body-sm)] [&_svg]:size-4\",\n icon: \"size-9 [&_svg]:size-4\",\n \"icon-sm\": \"size-8 rounded-[var(--ds-radius-sm)] [&_svg]:size-3.5\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ComponentProps<typeof BaseButton>, VariantProps<typeof buttonVariants> {}\n\nfunction Button({ className, variant, size, ...props }: ButtonProps) {\n return (\n <BaseButton\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;;;;;;AAOA,SAAgB,GAAG,GAAG,QAA8B;CAClD,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;;;;;;;;;;;ACQA,MAAM,iBAAiB,IACrB;CACE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GACA;CACE,UAAU;EACR,SAAS;GACP,SACE;GACF,WACE;GACF,SACE;GACF,OACE;GACF,MACE;GACF,aACE;GACF,MAAM;EACR;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,MAAM;GACN,WAAW;EACb;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAKA,SAAS,OAAO,EAAE,WAAW,SAAS,MAAM,GAAG,SAAsB;CACnE,OACE,oBAACA,UAAD;EACE,aAAU;EACV,WAAW,GAAG,eAAe;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAC1D,GAAI;CACL,CAAA;AAEL"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "cortena-ui",
3
+ "version": "0.1.0",
4
+ "description": "Cortena UI — the shared React component library for cortenaweb and every Cortena extension. shadcn over Base UI, styled entirely from cortena-design tokens.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/AscendendenceAI/cortena-design.git",
9
+ "directory": "packages/ui"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": [
13
+ "*.css"
14
+ ],
15
+ "engines": {
16
+ "node": ">=22"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "README.md"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./styles.css": "./src/styles/index.css",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown",
33
+ "check": "tsc --noEmit",
34
+ "lint:design": "node ../../lint/cli.mjs src",
35
+ "test": "vitest run",
36
+ "guide": "vite --config guide/vite.config.ts",
37
+ "guide:build": "vite build --config guide/vite.config.ts",
38
+ "prepack": "pnpm build"
39
+ },
40
+ "peerDependencies": {
41
+ "cortena-design": ">=3.1.0",
42
+ "react": "^19.0.0",
43
+ "react-dom": "^19.0.0"
44
+ },
45
+ "dependencies": {
46
+ "@base-ui/react": "^1.8.0",
47
+ "class-variance-authority": "^0.7.1",
48
+ "clsx": "^2.1.1",
49
+ "lucide-react": "^1.41.0",
50
+ "tailwind-merge": "^3.6.0",
51
+ "tw-animate-css": "^1.4.0"
52
+ },
53
+ "devDependencies": {
54
+ "@tailwindcss/vite": "^4.3.3",
55
+ "@types/node": "^22.20.1",
56
+ "@types/react": "^19.1.0",
57
+ "@types/react-dom": "^19.1.0",
58
+ "@vitejs/plugin-react": "^6.1.1",
59
+ "@vitest/browser-playwright": "^5.0.0",
60
+ "cortena-design": "workspace:*",
61
+ "playwright": "^1.63.0",
62
+ "react": "^19.1.0",
63
+ "react-dom": "^19.1.0",
64
+ "tailwindcss": "^4.3.3",
65
+ "tsdown": "^0.23.0",
66
+ "typescript": "^5.9.0",
67
+ "vite": "^8.2.2",
68
+ "vitest": "^5.0.0",
69
+ "vitest-browser-react": "^2.3.0"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ }
74
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import { Button as BaseButton } from "@base-ui/react/button";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import type * as React from "react";
6
+ import { cn } from "@/lib/cn";
7
+
8
+ /**
9
+ * Button.
10
+ *
11
+ * Base UI supplies the behaviour (native button semantics, `focusableWhenDisabled`,
12
+ * keyboard activation on non-button elements through `render`). Every visual
13
+ * value is a cortena-design token; nothing here is a literal the lint would flag.
14
+ *
15
+ * Radix users: `asChild` is gone. Pass `render={<a href=... />}` to change the
16
+ * rendered element, which is Base UI's equivalent.
17
+ */
18
+ const buttonVariants = cva(
19
+ [
20
+ "inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap",
21
+ "font-medium",
22
+ "rounded-[var(--ds-radius-md)]",
23
+ "border",
24
+ "transition-[background-color,border-color,color,box-shadow,filter]",
25
+ "duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]",
26
+ "outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40",
27
+ "disabled:pointer-events-none disabled:opacity-50",
28
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
29
+ ],
30
+ {
31
+ variants: {
32
+ variant: {
33
+ primary:
34
+ "border-[var(--ds-primary)] bg-[var(--ds-primary)] text-[color:var(--ds-primary-foreground)] hover:border-[var(--ds-primary-hover)] hover:bg-[var(--ds-primary-hover)]",
35
+ secondary:
36
+ "border-[var(--ds-border)] bg-[var(--ds-secondary)] text-[color:var(--ds-secondary-foreground)] hover:border-[var(--ds-border-strong)] hover:bg-[var(--ds-hover)]",
37
+ outline:
38
+ "border-[var(--ds-border-strong)] bg-transparent text-[color:var(--ds-foreground)] hover:bg-[var(--ds-hover)]",
39
+ ghost:
40
+ "border-transparent bg-transparent text-[color:var(--ds-muted-foreground)] hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]",
41
+ soft:
42
+ "border-transparent bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary-soft-foreground)] hover:bg-[var(--ds-primary-soft)] hover:brightness-95",
43
+ destructive:
44
+ "border-[var(--ds-destructive)] bg-[var(--ds-destructive)] text-[color:var(--ds-destructive-foreground)] hover:brightness-110",
45
+ link: "border-transparent bg-transparent text-[color:var(--ds-primary)] underline-offset-4 hover:underline",
46
+ },
47
+ size: {
48
+ sm: "h-8 px-3 text-[length:var(--ds-text-caption)] rounded-[var(--ds-radius-sm)] [&_svg]:size-3.5",
49
+ md: "h-9 px-4 text-[length:var(--ds-text-caption-lg)] [&_svg]:size-4",
50
+ lg: "h-10 px-5 text-[length:var(--ds-text-body-sm)] [&_svg]:size-4",
51
+ icon: "size-9 [&_svg]:size-4",
52
+ "icon-sm": "size-8 rounded-[var(--ds-radius-sm)] [&_svg]:size-3.5",
53
+ },
54
+ },
55
+ defaultVariants: {
56
+ variant: "primary",
57
+ size: "md",
58
+ },
59
+ },
60
+ );
61
+
62
+ export interface ButtonProps
63
+ extends React.ComponentProps<typeof BaseButton>, VariantProps<typeof buttonVariants> {}
64
+
65
+ function Button({ className, variant, size, ...props }: ButtonProps) {
66
+ return (
67
+ <BaseButton
68
+ data-slot="button"
69
+ className={cn(buttonVariants({ variant, size }), className)}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ export { Button, buttonVariants };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // cortena-ui public surface. Components are added here as they land
2
+ // (DESIGN-4 onward); everything exported is styled from cortena-design tokens.
3
+ export { cn } from "@/lib/cn";
4
+ export { Button, buttonVariants, type ButtonProps } from "@/components/button";
package/src/lib/cn.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * Merge class names the shadcn way: clsx for conditionals, tailwind-merge so a
6
+ * consumer's `className` wins over a component's default for the same utility.
7
+ */
8
+ export function cn(...inputs: ClassValue[]): string {
9
+ return twMerge(clsx(inputs));
10
+ }
@@ -0,0 +1,25 @@
1
+ /*
2
+ * cortena-ui/styles.css
3
+ *
4
+ * Import this AFTER the token layer, in the consumer's Tailwind entry file:
5
+ *
6
+ * @import "tailwindcss";
7
+ * @import "cortena-design/fonts.css";
8
+ * @import "cortena-design/tokens.css";
9
+ * @import "cortena-ui/styles.css";
10
+ * @source "../node_modules/cortena-ui/src";
11
+ *
12
+ * It carries only what components need beyond the tokens: the enter/exit
13
+ * animation utilities shadcn components rely on, and a custom variant for
14
+ * Base UI's data attributes. Every colour, size, radius, shadow and duration
15
+ * comes from cortena-design; nothing here restates a token. The `@source`
16
+ * line is what makes the consumer's Tailwind build generate the utility
17
+ * classes used inside this package's components.
18
+ */
19
+ @import "tw-animate-css";
20
+
21
+ /* Base UI marks open/closed state with data-open / data-closed rather than
22
+ Radix's data-state, so give components a variant for each. */
23
+ @custom-variant open (&[data-open]);
24
+ @custom-variant closed (&[data-closed]);
25
+ @custom-variant popup-open (&[data-popup-open]);