cortena-ui 0.1.0 → 1.0.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/dist/index.d.ts +2004 -4
- package/dist/index.js +5949 -2
- package/dist/index.js.map +1 -1
- package/package.json +41 -5
- package/src/components/accordion.tsx +86 -0
- package/src/components/alert.tsx +81 -0
- package/src/components/avatar.tsx +53 -0
- package/src/components/badge.tsx +51 -0
- package/src/components/calendar.tsx +193 -0
- package/src/components/card.tsx +85 -0
- package/src/components/chart/chart.tsx +193 -0
- package/src/components/chart/container.tsx +324 -0
- package/src/components/chart/data.ts +101 -0
- package/src/components/chart/index.tsx +84 -0
- package/src/components/chart/nivo-charts.tsx +227 -0
- package/src/components/chart/nivo-theme.ts +136 -0
- package/src/components/chart/recharts-charts.tsx +232 -0
- package/src/components/chart/tokens.ts +210 -0
- package/src/components/chart/types.ts +142 -0
- package/src/components/checkbox.tsx +49 -0
- package/src/components/chip.tsx +49 -0
- package/src/components/combobox.tsx +405 -0
- package/src/components/command.tsx +225 -0
- package/src/components/data-table/data-table.tsx +731 -0
- package/src/components/data-table/export.ts +121 -0
- package/src/components/data-table/features.ts +123 -0
- package/src/components/data-table/index.tsx +45 -0
- package/src/components/data-table/parts.tsx +568 -0
- package/src/components/data-table/system-columns.tsx +91 -0
- package/src/components/data-table/types.ts +182 -0
- package/src/components/data-table/use-data-table.ts +224 -0
- package/src/components/data-table/use-server-source.ts +232 -0
- package/src/components/date-field.tsx +327 -0
- package/src/components/date-picker.tsx +230 -0
- package/src/components/dialog.tsx +146 -0
- package/src/components/dropdown-menu.tsx +314 -0
- package/src/components/dropzone.tsx +260 -0
- package/src/components/empty-state.tsx +91 -0
- package/src/components/error-banner.tsx +65 -0
- package/src/components/field.tsx +250 -0
- package/src/components/form.tsx +212 -0
- package/src/components/input.tsx +32 -0
- package/src/components/kbd.tsx +26 -0
- package/src/components/label.tsx +26 -0
- package/src/components/loading-skeleton.tsx +153 -0
- package/src/components/markdown.tsx +377 -0
- package/src/components/page-header.tsx +99 -0
- package/src/components/popover.tsx +133 -0
- package/src/components/progress.tsx +83 -0
- package/src/components/radio-group.tsx +48 -0
- package/src/components/scroll-area.tsx +74 -0
- package/src/components/section-card.tsx +75 -0
- package/src/components/segmented.tsx +81 -0
- package/src/components/select.tsx +222 -0
- package/src/components/separator.tsx +41 -0
- package/src/components/sheet.tsx +161 -0
- package/src/components/skeleton.tsx +22 -0
- package/src/components/sortable-list.tsx +275 -0
- package/src/components/spinner.tsx +32 -0
- package/src/components/status-dot.tsx +78 -0
- package/src/components/switch.tsx +48 -0
- package/src/components/tabs.tsx +109 -0
- package/src/components/textarea.tsx +75 -0
- package/src/components/toast.tsx +150 -0
- package/src/components/toolbar.tsx +63 -0
- package/src/components/tooltip.tsx +103 -0
- package/src/hooks/use-cortena-theme.ts +268 -0
- package/src/index.ts +91 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortena-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
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
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -31,27 +31,54 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsdown",
|
|
33
33
|
"check": "tsc --noEmit",
|
|
34
|
-
"lint:design": "node ../../lint/cli.mjs src",
|
|
34
|
+
"lint:design": "node ../../lint/cli.mjs --exempt chart-import src",
|
|
35
35
|
"test": "vitest run",
|
|
36
36
|
"guide": "vite --config guide/vite.config.ts",
|
|
37
37
|
"guide:build": "vite build --config guide/vite.config.ts",
|
|
38
38
|
"prepack": "pnpm build"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"cortena-design": ">=3.
|
|
41
|
+
"cortena-design": ">=3.2.0",
|
|
42
42
|
"react": "^19.0.0",
|
|
43
|
-
"react-dom": "^19.0.0"
|
|
43
|
+
"react-dom": "^19.0.0",
|
|
44
|
+
"zod": "^3.25.0 || ^4.0.0",
|
|
45
|
+
"@tanstack/react-query": "^5.0.0"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@base-ui/react": "^1.8.0",
|
|
49
|
+
"@dnd-kit/core": "^6.3.1",
|
|
50
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
51
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
52
|
+
"@hookform/resolvers": "^5.9.1",
|
|
53
|
+
"@nivo/bar": "^0.99.0",
|
|
54
|
+
"@nivo/calendar": "^0.99.0",
|
|
55
|
+
"@nivo/core": "^0.99.0",
|
|
56
|
+
"@nivo/heatmap": "^0.99.0",
|
|
57
|
+
"@nivo/line": "^0.99.0",
|
|
58
|
+
"@nivo/pie": "^0.99.0",
|
|
59
|
+
"@nivo/scatterplot": "^0.99.0",
|
|
60
|
+
"@nivo/sunburst": "^0.99.0",
|
|
61
|
+
"@nivo/treemap": "^0.99.0",
|
|
62
|
+
"@tanstack/react-table": "^9.2.4",
|
|
63
|
+
"@tanstack/react-virtual": "^3.14.10",
|
|
47
64
|
"class-variance-authority": "^0.7.1",
|
|
48
65
|
"clsx": "^2.1.1",
|
|
66
|
+
"cmdk": "^1.1.1",
|
|
67
|
+
"exceljs": "^4.4.0",
|
|
49
68
|
"lucide-react": "^1.41.0",
|
|
69
|
+
"react-day-picker": "^10.0.1",
|
|
70
|
+
"react-dropzone": "^20.1.1",
|
|
71
|
+
"react-hook-form": "^7.87.0",
|
|
72
|
+
"react-markdown": "^10.1.0",
|
|
73
|
+
"recharts": "^3.10.1",
|
|
74
|
+
"rehype-sanitize": "^6.0.0",
|
|
75
|
+
"remark-gfm": "^4.0.1",
|
|
50
76
|
"tailwind-merge": "^3.6.0",
|
|
51
77
|
"tw-animate-css": "^1.4.0"
|
|
52
78
|
},
|
|
53
79
|
"devDependencies": {
|
|
54
80
|
"@tailwindcss/vite": "^4.3.3",
|
|
81
|
+
"@tanstack/react-query": "^5.102.8",
|
|
55
82
|
"@types/node": "^22.20.1",
|
|
56
83
|
"@types/react": "^19.1.0",
|
|
57
84
|
"@types/react-dom": "^19.1.0",
|
|
@@ -66,9 +93,18 @@
|
|
|
66
93
|
"typescript": "^5.9.0",
|
|
67
94
|
"vite": "^8.2.2",
|
|
68
95
|
"vitest": "^5.0.0",
|
|
69
|
-
"vitest-browser-react": "^2.3.0"
|
|
96
|
+
"vitest-browser-react": "^2.3.0",
|
|
97
|
+
"zod": "^4.5.4"
|
|
70
98
|
},
|
|
71
99
|
"publishConfig": {
|
|
72
100
|
"access": "public"
|
|
101
|
+
},
|
|
102
|
+
"peerDependenciesMeta": {
|
|
103
|
+
"zod": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"@tanstack/react-query": {
|
|
107
|
+
"optional": true
|
|
108
|
+
}
|
|
73
109
|
}
|
|
74
110
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Accordion.
|
|
9
|
+
*
|
|
10
|
+
* Base UI's Accordion is a list of Collapsibles: `Root > Item > Header > Trigger`
|
|
11
|
+
* plus `Panel`. The panel animates on height through Base UI's
|
|
12
|
+
* `--accordion-panel-height`, and marks its first/last frame with
|
|
13
|
+
* `data-starting-style` / `data-ending-style`, which is where it collapses to 0.
|
|
14
|
+
*
|
|
15
|
+
* Radix users: `type="single" | "multiple"` is now the boolean `multiple`, and
|
|
16
|
+
* `value` is always an array. `collapsible` is the default. `data-state="open"`
|
|
17
|
+
* is `data-open` on the item and panel, `data-panel-open` on the trigger.
|
|
18
|
+
*/
|
|
19
|
+
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
|
20
|
+
return (
|
|
21
|
+
<AccordionPrimitive.Root
|
|
22
|
+
data-slot="accordion"
|
|
23
|
+
className={cn("flex w-full flex-col", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
30
|
+
return (
|
|
31
|
+
<AccordionPrimitive.Item
|
|
32
|
+
data-slot="accordion-item"
|
|
33
|
+
className={cn("border-b border-[var(--ds-border)] last:border-b-0", className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {
|
|
40
|
+
return (
|
|
41
|
+
<AccordionPrimitive.Header data-slot="accordion-header" className="flex">
|
|
42
|
+
<AccordionPrimitive.Trigger
|
|
43
|
+
data-slot="accordion-trigger"
|
|
44
|
+
className={cn(
|
|
45
|
+
"flex flex-1 items-start justify-between gap-4 py-4 text-left",
|
|
46
|
+
"text-[length:var(--ds-text-body-sm)] font-medium text-[color:var(--ds-foreground)]",
|
|
47
|
+
"rounded-[var(--ds-radius-sm)] outline-none",
|
|
48
|
+
"transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]",
|
|
49
|
+
"hover:underline focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40",
|
|
50
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
51
|
+
"[&[data-panel-open]>svg]:rotate-180",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
<ChevronDown
|
|
58
|
+
aria-hidden
|
|
59
|
+
className={cn(
|
|
60
|
+
"pointer-events-none size-4 shrink-0 translate-y-0.5 text-[color:var(--ds-muted-foreground)]",
|
|
61
|
+
"transition-transform duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]",
|
|
62
|
+
)}
|
|
63
|
+
/>
|
|
64
|
+
</AccordionPrimitive.Trigger>
|
|
65
|
+
</AccordionPrimitive.Header>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props) {
|
|
70
|
+
return (
|
|
71
|
+
<AccordionPrimitive.Panel
|
|
72
|
+
data-slot="accordion-content"
|
|
73
|
+
className={cn(
|
|
74
|
+
"h-[var(--accordion-panel-height,auto)] overflow-hidden",
|
|
75
|
+
"text-[length:var(--ds-text-body-sm)] text-[color:var(--ds-muted-foreground)]",
|
|
76
|
+
"transition-[height] duration-[var(--ds-duration-base)] ease-[var(--ds-ease-out)]",
|
|
77
|
+
"data-[starting-style]:h-0 data-[ending-style]:h-0",
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
>
|
|
81
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
82
|
+
</AccordionPrimitive.Panel>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Alert — an inline callout. Tinted variants use the `-soft` fill for the
|
|
9
|
+
* background and the tone colour for the border and icon; the body text stays
|
|
10
|
+
* on the foreground token so it reads in both themes.
|
|
11
|
+
*/
|
|
12
|
+
const alertVariants = cva(
|
|
13
|
+
[
|
|
14
|
+
"flex gap-3 rounded-[var(--ds-radius-md)] border px-3.5 py-3",
|
|
15
|
+
"text-[length:var(--ds-text-caption-lg)] leading-[var(--ds-text-caption-lg--line-height)]",
|
|
16
|
+
"text-[color:var(--ds-foreground)]",
|
|
17
|
+
],
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default:
|
|
22
|
+
"bg-[var(--ds-card)] border-[var(--ds-border-subtle)] [&_[data-slot=alert-icon]]:text-[color:var(--ds-muted-foreground)]",
|
|
23
|
+
success:
|
|
24
|
+
"bg-[var(--ds-success-soft)] border-[var(--ds-success)]/35 [&_[data-slot=alert-icon]]:text-[color:var(--ds-success)]",
|
|
25
|
+
warn: "bg-[var(--ds-warning-soft)] border-[var(--ds-warning)]/35 [&_[data-slot=alert-icon]]:text-[color:var(--ds-warning)]",
|
|
26
|
+
danger:
|
|
27
|
+
"bg-[var(--ds-destructive-soft)] border-[var(--ds-destructive)]/35 [&_[data-slot=alert-icon]]:text-[color:var(--ds-destructive)]",
|
|
28
|
+
info: "bg-[var(--ds-info-soft)] border-[var(--ds-info)]/35 [&_[data-slot=alert-icon]]:text-[color:var(--ds-info)]",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
variant: "default",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export interface AlertProps
|
|
38
|
+
extends React.ComponentProps<"div">, VariantProps<typeof alertVariants> {}
|
|
39
|
+
|
|
40
|
+
function Alert({ className, variant, ...props }: AlertProps) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-slot="alert"
|
|
44
|
+
role="alert"
|
|
45
|
+
className={cn(alertVariants({ variant }), className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function AlertIcon({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="alert-icon"
|
|
55
|
+
className={cn("shrink-0 self-start [&_svg]:size-4.5 [&_svg]:shrink-0", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
data-slot="alert-title"
|
|
65
|
+
className={cn("self-start font-semibold text-[color:var(--ds-foreground)]", className)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
data-slot="alert-description"
|
|
75
|
+
className={cn("mt-0.5 self-start text-[color:var(--ds-muted-foreground)]", className)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { Alert, AlertIcon, AlertTitle, AlertDescription, alertVariants };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Avatar.
|
|
8
|
+
*
|
|
9
|
+
* Base UI preloads the image and only mounts `<img>` once it has loaded, so
|
|
10
|
+
* the fallback is what paints while loading and after an error. Initials sit
|
|
11
|
+
* on the primary fill in inverse text.
|
|
12
|
+
*
|
|
13
|
+
* Radix users: `delayMs` on the fallback is now `delay`.
|
|
14
|
+
*/
|
|
15
|
+
function Avatar({ className, ...props }: AvatarPrimitive.Root.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<AvatarPrimitive.Root
|
|
18
|
+
data-slot="avatar"
|
|
19
|
+
className={cn(
|
|
20
|
+
"relative flex size-8 shrink-0 overflow-hidden rounded-[var(--ds-radius-full)]",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|
29
|
+
return (
|
|
30
|
+
<AvatarPrimitive.Image
|
|
31
|
+
data-slot="avatar-image"
|
|
32
|
+
className={cn("aspect-square size-full object-cover", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {
|
|
39
|
+
return (
|
|
40
|
+
<AvatarPrimitive.Fallback
|
|
41
|
+
data-slot="avatar-fallback"
|
|
42
|
+
className={cn(
|
|
43
|
+
"flex size-full select-none items-center justify-center rounded-[var(--ds-radius-full)]",
|
|
44
|
+
"bg-[var(--ds-primary)] text-[color:var(--ds-primary-foreground)]",
|
|
45
|
+
"text-[length:var(--ds-text-caption-lg)] font-semibold tracking-tight",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Badge — the mono, uppercase status label. Each tone pairs a `-soft` fill
|
|
9
|
+
* with its foreground token; `warn`, `danger` and `secondary` are aliases kept
|
|
10
|
+
* for cortenaweb call sites.
|
|
11
|
+
*/
|
|
12
|
+
const badgeVariants = cva(
|
|
13
|
+
[
|
|
14
|
+
"inline-flex items-center gap-1.5 px-1.5 py-0.5 whitespace-nowrap",
|
|
15
|
+
"rounded-[var(--ds-radius-sm)]",
|
|
16
|
+
"font-[family-name:var(--ds-font-mono)] text-[length:var(--ds-text-caption-sm)] font-medium uppercase",
|
|
17
|
+
"tracking-[var(--ds-text-eyebrow--letter-spacing)] leading-[var(--ds-text-caption-sm--line-height)]",
|
|
18
|
+
"[&_svg]:size-3 [&_svg]:shrink-0",
|
|
19
|
+
],
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
variant: {
|
|
23
|
+
default: "bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary-soft-foreground)]",
|
|
24
|
+
success: "bg-[var(--ds-success-soft)] text-[color:var(--ds-success)]",
|
|
25
|
+
warning: "bg-[var(--ds-warning-soft)] text-[color:var(--ds-warning)]",
|
|
26
|
+
warn: "bg-[var(--ds-warning-soft)] text-[color:var(--ds-warning)]",
|
|
27
|
+
danger: "bg-[var(--ds-destructive-soft)] text-[color:var(--ds-destructive)]",
|
|
28
|
+
destructive: "bg-[var(--ds-destructive-soft)] text-[color:var(--ds-destructive)]",
|
|
29
|
+
info: "bg-[var(--ds-info-soft)] text-[color:var(--ds-info)]",
|
|
30
|
+
neutral: "bg-[var(--ds-hover)] text-[color:var(--ds-muted-foreground)]",
|
|
31
|
+
secondary: "bg-[var(--ds-hover)] text-[color:var(--ds-muted-foreground)]",
|
|
32
|
+
outline:
|
|
33
|
+
"bg-transparent border border-[var(--ds-border)] text-[color:var(--ds-muted-foreground)]",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
variant: "default",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export interface BadgeProps
|
|
43
|
+
extends React.ComponentProps<"span">, VariantProps<typeof badgeVariants> {}
|
|
44
|
+
|
|
45
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
46
|
+
return (
|
|
47
|
+
<span data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import {
|
|
6
|
+
DayPicker,
|
|
7
|
+
getDefaultClassNames,
|
|
8
|
+
type ChevronProps,
|
|
9
|
+
type DayButtonProps,
|
|
10
|
+
type DayPickerProps,
|
|
11
|
+
type NextMonthButtonProps,
|
|
12
|
+
type PreviousMonthButtonProps,
|
|
13
|
+
type RootProps,
|
|
14
|
+
} from "react-day-picker";
|
|
15
|
+
import { Button } from "@/components/button";
|
|
16
|
+
import { cn } from "@/lib/cn";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Calendar.
|
|
20
|
+
*
|
|
21
|
+
* The shadcn Calendar shape over react-day-picker 10. The library's stylesheet
|
|
22
|
+
* is not imported; every class comes from the `classNames` map below and every
|
|
23
|
+
* visual value in it is a cortena-design token. Structure (nav, caption, grid,
|
|
24
|
+
* dropdown caption) is DayPicker's; the day cells, nav buttons and chevrons are
|
|
25
|
+
* swapped for our Button and lucide icons through `components`.
|
|
26
|
+
*
|
|
27
|
+
* `showOutsideDays` defaults to true and `captionLayout` to `"label"`; pass
|
|
28
|
+
* `captionLayout="dropdown"` for native month/year selects in the caption.
|
|
29
|
+
*/
|
|
30
|
+
export type CalendarProps = DayPickerProps;
|
|
31
|
+
|
|
32
|
+
function CalendarRoot({ className, rootRef, ...props }: RootProps) {
|
|
33
|
+
return <div data-slot="calendar" ref={rootRef} className={className} {...props} />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CalendarChevron({ className, orientation, size: _size, disabled, ...props }: ChevronProps) {
|
|
37
|
+
const Icon =
|
|
38
|
+
orientation === "left"
|
|
39
|
+
? ChevronLeft
|
|
40
|
+
: orientation === "right"
|
|
41
|
+
? ChevronRight
|
|
42
|
+
: orientation === "up"
|
|
43
|
+
? ChevronUp
|
|
44
|
+
: ChevronDown;
|
|
45
|
+
return (
|
|
46
|
+
<Icon
|
|
47
|
+
data-slot="calendar-chevron"
|
|
48
|
+
aria-hidden
|
|
49
|
+
className={cn("size-4", disabled && "opacity-50", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function CalendarPreviousMonthButton(props: PreviousMonthButtonProps) {
|
|
56
|
+
return <Button data-slot="calendar-previous-month" variant="outline" size="icon-sm" {...props} />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function CalendarNextMonthButton(props: NextMonthButtonProps) {
|
|
60
|
+
return <Button data-slot="calendar-next-month" variant="outline" size="icon-sm" {...props} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One day cell's button. Selection and range state are exposed as data
|
|
65
|
+
* attributes so the classes below (and a consumer's) can paint them; the
|
|
66
|
+
* `today` attribute is only set when the day is not selected, so the soft
|
|
67
|
+
* fill never competes with the selected fill.
|
|
68
|
+
*/
|
|
69
|
+
function CalendarDayButton({ className, day, modifiers, ...props }: DayButtonProps) {
|
|
70
|
+
const ref = React.useRef<HTMLButtonElement>(null);
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (modifiers.focused) ref.current?.focus();
|
|
73
|
+
}, [modifiers.focused]);
|
|
74
|
+
|
|
75
|
+
const inRange = modifiers.range_start || modifiers.range_end || modifiers.range_middle;
|
|
76
|
+
const selectedSingle = modifiers.selected && !inRange;
|
|
77
|
+
const filled = selectedSingle || modifiers.range_start || modifiers.range_end;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Button
|
|
81
|
+
ref={ref}
|
|
82
|
+
data-slot="calendar-day-button"
|
|
83
|
+
data-day={day.isoDate}
|
|
84
|
+
data-selected-single={selectedSingle || undefined}
|
|
85
|
+
data-range-start={modifiers.range_start || undefined}
|
|
86
|
+
data-range-end={modifiers.range_end || undefined}
|
|
87
|
+
data-range-middle={(modifiers.range_middle && !filled) || undefined}
|
|
88
|
+
data-today={(modifiers.today && !modifiers.selected) || undefined}
|
|
89
|
+
variant="ghost"
|
|
90
|
+
size="icon-sm"
|
|
91
|
+
className={cn(
|
|
92
|
+
"size-8 font-normal text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-foreground)]",
|
|
93
|
+
"rounded-[var(--ds-radius-sm)] leading-none",
|
|
94
|
+
// today: soft fill from the primary tint
|
|
95
|
+
"data-[today]:bg-[var(--ds-primary-soft)] data-[today]:text-[color:var(--ds-primary-soft-foreground)]",
|
|
96
|
+
// range interior: soft fill, square so the run reads as one band
|
|
97
|
+
"data-[range-middle]:rounded-none data-[range-middle]:bg-[var(--ds-primary-soft)] data-[range-middle]:text-[color:var(--ds-primary-soft-foreground)] data-[range-middle]:hover:bg-[var(--ds-primary-soft)] data-[range-middle]:hover:brightness-95",
|
|
98
|
+
// selected day and range ends: solid primary
|
|
99
|
+
"data-[selected-single]:bg-[var(--ds-primary)] data-[selected-single]:text-[color:var(--ds-primary-foreground)] data-[selected-single]:hover:bg-[var(--ds-primary-hover)] data-[selected-single]:hover:text-[color:var(--ds-primary-foreground)]",
|
|
100
|
+
"data-[range-start]:bg-[var(--ds-primary)] data-[range-start]:text-[color:var(--ds-primary-foreground)] data-[range-start]:hover:bg-[var(--ds-primary-hover)] data-[range-start]:hover:text-[color:var(--ds-primary-foreground)]",
|
|
101
|
+
"data-[range-end]:bg-[var(--ds-primary)] data-[range-end]:text-[color:var(--ds-primary-foreground)] data-[range-end]:hover:bg-[var(--ds-primary-hover)] data-[range-end]:hover:text-[color:var(--ds-primary-foreground)]",
|
|
102
|
+
// the cell's own flags, read from the parent <td>
|
|
103
|
+
"group-data-[outside]/day:text-[color:var(--ds-text-tertiary)]",
|
|
104
|
+
"group-data-[disabled]/day:text-[color:var(--ds-text-disabled)] group-data-[disabled]/day:opacity-50",
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function Calendar({
|
|
113
|
+
className,
|
|
114
|
+
classNames,
|
|
115
|
+
components,
|
|
116
|
+
showOutsideDays = true,
|
|
117
|
+
captionLayout = "label",
|
|
118
|
+
formatters,
|
|
119
|
+
...props
|
|
120
|
+
}: CalendarProps) {
|
|
121
|
+
const defaults = getDefaultClassNames();
|
|
122
|
+
return (
|
|
123
|
+
<DayPicker
|
|
124
|
+
showOutsideDays={showOutsideDays}
|
|
125
|
+
captionLayout={captionLayout}
|
|
126
|
+
className={cn("w-fit p-3 text-[color:var(--ds-foreground)]", className)}
|
|
127
|
+
formatters={{
|
|
128
|
+
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
|
|
129
|
+
...formatters,
|
|
130
|
+
}}
|
|
131
|
+
classNames={{
|
|
132
|
+
root: cn("w-fit", defaults.root),
|
|
133
|
+
months: cn("relative flex flex-col gap-4 md:flex-row", defaults.months),
|
|
134
|
+
month: cn("flex w-full flex-col gap-3", defaults.month),
|
|
135
|
+
nav: cn("absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", defaults.nav),
|
|
136
|
+
button_previous: cn("aria-disabled:pointer-events-none aria-disabled:opacity-50", defaults.button_previous),
|
|
137
|
+
button_next: cn("aria-disabled:pointer-events-none aria-disabled:opacity-50", defaults.button_next),
|
|
138
|
+
month_caption: cn("flex h-8 w-full items-center justify-center px-9", defaults.month_caption),
|
|
139
|
+
caption_label: cn(
|
|
140
|
+
"select-none font-medium text-[length:var(--ds-text-body-sm)] text-[color:var(--ds-foreground)]",
|
|
141
|
+
captionLayout !== "label" &&
|
|
142
|
+
"flex h-8 items-center gap-1 rounded-[var(--ds-radius-sm)] pl-2 pr-1 [&>svg]:size-3.5 [&>svg]:text-[color:var(--ds-muted-foreground)]",
|
|
143
|
+
defaults.caption_label,
|
|
144
|
+
),
|
|
145
|
+
dropdowns: cn(
|
|
146
|
+
"flex h-8 w-full items-center justify-center gap-1.5 font-medium text-[length:var(--ds-text-body-sm)]",
|
|
147
|
+
defaults.dropdowns,
|
|
148
|
+
),
|
|
149
|
+
dropdown_root: cn(
|
|
150
|
+
"relative rounded-[var(--ds-radius-sm)] border border-[var(--ds-border)] bg-[var(--ds-muted)]",
|
|
151
|
+
"has-focus-visible:border-[var(--ds-ring)] has-focus-visible:ring-[3px] has-focus-visible:ring-[var(--ds-ring)]/40",
|
|
152
|
+
defaults.dropdown_root,
|
|
153
|
+
),
|
|
154
|
+
dropdown: cn("absolute inset-0 cursor-pointer opacity-0", defaults.dropdown),
|
|
155
|
+
month_grid: cn("w-full border-collapse", defaults.month_grid),
|
|
156
|
+
weekdays: cn("flex", defaults.weekdays),
|
|
157
|
+
weekday: cn(
|
|
158
|
+
"flex-1 select-none font-normal text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
159
|
+
defaults.weekday,
|
|
160
|
+
),
|
|
161
|
+
week: cn("mt-1 flex w-full", defaults.week),
|
|
162
|
+
week_number_header: cn("w-8 select-none", defaults.week_number_header),
|
|
163
|
+
week_number: cn(
|
|
164
|
+
"select-none text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
165
|
+
defaults.week_number,
|
|
166
|
+
),
|
|
167
|
+
day: cn(
|
|
168
|
+
"group/day relative aspect-square size-8 select-none p-0 text-center",
|
|
169
|
+
defaults.day,
|
|
170
|
+
),
|
|
171
|
+
range_start: cn("rounded-l-[var(--ds-radius-sm)] bg-[var(--ds-primary-soft)]", defaults.range_start),
|
|
172
|
+
range_middle: cn("rounded-none bg-[var(--ds-primary-soft)]", defaults.range_middle),
|
|
173
|
+
range_end: cn("rounded-r-[var(--ds-radius-sm)] bg-[var(--ds-primary-soft)]", defaults.range_end),
|
|
174
|
+
today: defaults.today,
|
|
175
|
+
outside: defaults.outside,
|
|
176
|
+
disabled: defaults.disabled,
|
|
177
|
+
hidden: cn("invisible", defaults.hidden),
|
|
178
|
+
...classNames,
|
|
179
|
+
}}
|
|
180
|
+
components={{
|
|
181
|
+
Root: CalendarRoot,
|
|
182
|
+
Chevron: CalendarChevron,
|
|
183
|
+
PreviousMonthButton: CalendarPreviousMonthButton,
|
|
184
|
+
NextMonthButton: CalendarNextMonthButton,
|
|
185
|
+
DayButton: CalendarDayButton,
|
|
186
|
+
...components,
|
|
187
|
+
}}
|
|
188
|
+
{...props}
|
|
189
|
+
/>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export { Calendar, CalendarDayButton };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
|
|
6
|
+
export interface CardProps extends React.ComponentProps<"div"> {
|
|
7
|
+
/** Lift the border and surface on hover, for clickable cards. */
|
|
8
|
+
hover?: boolean;
|
|
9
|
+
/** Draw the border in the ring colour, for a selected or highlighted card. */
|
|
10
|
+
accent?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function Card({ className, hover, accent, ...props }: CardProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
data-slot="card"
|
|
17
|
+
data-accent={accent ? "" : undefined}
|
|
18
|
+
className={cn(
|
|
19
|
+
"flex flex-col rounded-[var(--ds-radius-lg)] border bg-[var(--ds-card)] text-[color:var(--ds-foreground)]",
|
|
20
|
+
"transition-[border-color,background-color] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]",
|
|
21
|
+
accent ? "border-[var(--ds-ring)]" : "border-[var(--ds-border-subtle)]",
|
|
22
|
+
hover && "hover:border-[var(--ds-border)] hover:bg-[var(--ds-popover)]",
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-slot="card-header"
|
|
34
|
+
className={cn("flex flex-col gap-1.5 p-5 pb-3", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-slot="card-title"
|
|
44
|
+
className={cn(
|
|
45
|
+
"text-[length:var(--ds-text-body-md)] font-semibold",
|
|
46
|
+
"leading-[var(--ds-text-h1--line-height)] tracking-[var(--ds-text-h1--letter-spacing)]",
|
|
47
|
+
"text-[color:var(--ds-foreground)]",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
data-slot="card-description"
|
|
59
|
+
className={cn(
|
|
60
|
+
"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]",
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
69
|
+
return <div data-slot="card-content" className={cn("p-5 pt-0", className)} {...props} />;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="card-footer"
|
|
76
|
+
className={cn(
|
|
77
|
+
"flex items-center gap-2 border-t border-[var(--ds-border-subtle)] p-4",
|
|
78
|
+
className,
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
|