@saena-io/create 0.1.0 → 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/dist/index.js +9 -9
- package/package.json +1 -1
- package/template/base/package.json +44 -2
- package/template/base/scripts/ui-update.ts +83 -0
- package/template/base/src/components/ui/accordion.tsx +75 -0
- package/template/base/src/components/ui/alert-dialog.tsx +162 -0
- package/template/base/src/components/ui/alert.tsx +73 -0
- package/template/base/src/components/ui/app-sidebar.tsx +183 -0
- package/template/base/src/components/ui/aspect-ratio.tsx +22 -0
- package/template/base/src/components/ui/asset-input.tsx +211 -0
- package/template/base/src/components/ui/avatar.tsx +91 -0
- package/template/base/src/components/ui/badge.tsx +50 -0
- package/template/base/src/components/ui/breadcrumb.tsx +104 -0
- package/template/base/src/components/ui/button-group.tsx +78 -0
- package/template/base/src/components/ui/button.tsx +56 -0
- package/template/base/src/components/ui/calendar.tsx +205 -0
- package/template/base/src/components/ui/card.tsx +85 -0
- package/template/base/src/components/ui/carousel.tsx +232 -0
- package/template/base/src/components/ui/chart.tsx +337 -0
- package/template/base/src/components/ui/checkbox.tsx +29 -0
- package/template/base/src/components/ui/collapsible.tsx +15 -0
- package/template/base/src/components/ui/combobox.tsx +276 -0
- package/template/base/src/components/ui/command.tsx +190 -0
- package/template/base/src/components/ui/context-menu.tsx +243 -0
- package/template/base/src/components/ui/dialog.tsx +134 -0
- package/template/base/src/components/ui/direction.tsx +4 -0
- package/template/base/src/components/ui/drawer.tsx +120 -0
- package/template/base/src/components/ui/dropdown-menu.tsx +254 -0
- package/template/base/src/components/ui/empty.tsx +94 -0
- package/template/base/src/components/ui/field.tsx +222 -0
- package/template/base/src/components/ui/focal-point-picker.tsx +175 -0
- package/template/base/src/components/ui/hover-card.tsx +46 -0
- package/template/base/src/components/ui/input-group.tsx +149 -0
- package/template/base/src/components/ui/input-otp.tsx +85 -0
- package/template/base/src/components/ui/input.tsx +20 -0
- package/template/base/src/components/ui/item.tsx +188 -0
- package/template/base/src/components/ui/kbd.tsx +26 -0
- package/template/base/src/components/ui/label.tsx +20 -0
- package/template/base/src/components/ui/menubar.tsx +268 -0
- package/template/base/src/components/ui/native-select.tsx +58 -0
- package/template/base/src/components/ui/nav-main.tsx +70 -0
- package/template/base/src/components/ui/nav-projects.tsx +97 -0
- package/template/base/src/components/ui/nav-secondary.tsx +37 -0
- package/template/base/src/components/ui/nav-user.tsx +108 -0
- package/template/base/src/components/ui/navigation-menu.tsx +164 -0
- package/template/base/src/components/ui/pagination.tsx +123 -0
- package/template/base/src/components/ui/popover.tsx +80 -0
- package/template/base/src/components/ui/progress.tsx +66 -0
- package/template/base/src/components/ui/radio-group.tsx +36 -0
- package/template/base/src/components/ui/resizable.tsx +42 -0
- package/template/base/src/components/ui/rich-text/ai-chat-editor.tsx +20 -0
- package/template/base/src/components/ui/rich-text/ai-command.tsx +90 -0
- package/template/base/src/components/ui/rich-text/ai-copilot.tsx +67 -0
- package/template/base/src/components/ui/rich-text/ai-menu.tsx +456 -0
- package/template/base/src/components/ui/rich-text/ai-node.tsx +42 -0
- package/template/base/src/components/ui/rich-text/ai-toolbar-button.tsx +29 -0
- package/template/base/src/components/ui/rich-text/block-draggable.tsx +187 -0
- package/template/base/src/components/ui/rich-text/block-selection.tsx +17 -0
- package/template/base/src/components/ui/rich-text/code-block-node.tsx +204 -0
- package/template/base/src/components/ui/rich-text/codec.ts +63 -0
- package/template/base/src/components/ui/rich-text/extension.ts +53 -0
- package/template/base/src/components/ui/rich-text/ghost-text.tsx +23 -0
- package/template/base/src/components/ui/rich-text/import-export-toolbar.tsx +103 -0
- package/template/base/src/components/ui/rich-text/link.tsx +18 -0
- package/template/base/src/components/ui/rich-text/list-node.tsx +65 -0
- package/template/base/src/components/ui/rich-text/nodes.tsx +44 -0
- package/template/base/src/components/ui/rich-text/plugins.ts +233 -0
- package/template/base/src/components/ui/rich-text/rich-text-editor.tsx +82 -0
- package/template/base/src/components/ui/rich-text/static.tsx +117 -0
- package/template/base/src/components/ui/rich-text/table-node.tsx +934 -0
- package/template/base/src/components/ui/rich-text/table-toolbar.tsx +232 -0
- package/template/base/src/components/ui/rich-text/toggle-node.tsx +36 -0
- package/template/base/src/components/ui/rich-text/toolbar-slots.ts +41 -0
- package/template/base/src/components/ui/rich-text/toolbar.tsx +668 -0
- package/template/base/src/components/ui/rich-text/use-ai-chat.ts +35 -0
- package/template/base/src/components/ui/rich-text/variable-type.ts +4 -0
- package/template/base/src/components/ui/rich-text/variable.tsx +97 -0
- package/template/base/src/components/ui/scroll-area.tsx +49 -0
- package/template/base/src/components/ui/select.tsx +202 -0
- package/template/base/src/components/ui/separator.tsx +19 -0
- package/template/base/src/components/ui/sheet.tsx +126 -0
- package/template/base/src/components/ui/sidebar.tsx +695 -0
- package/template/base/src/components/ui/skeleton.tsx +13 -0
- package/template/base/src/components/ui/slider.tsx +52 -0
- package/template/base/src/components/ui/sonner.tsx +50 -0
- package/template/base/src/components/ui/spinner.tsx +18 -0
- package/template/base/src/components/ui/switch.tsx +30 -0
- package/template/base/src/components/ui/table.tsx +89 -0
- package/template/base/src/components/ui/tabs.tsx +73 -0
- package/template/base/src/components/ui/textarea.tsx +18 -0
- package/template/base/src/components/ui/toggle-group.tsx +85 -0
- package/template/base/src/components/ui/toggle.tsx +45 -0
- package/template/base/src/components/ui/toolbar.tsx +451 -0
- package/template/base/src/components/ui/tooltip.tsx +52 -0
- package/template/base/src/hooks/use-mobile.ts +19 -0
- package/template/base/src/lib/utils.ts +6 -0
- package/template/base/src/routes/__root.tsx +1 -1
- package/template/base/src/server/auth.ts +2 -2
- package/template/base/src/styles/globals.css +230 -0
- package/template/base/vite.config.ts +15 -1
package/dist/index.js
CHANGED
|
@@ -31,8 +31,8 @@ var SAENA_VERSION = "^0.1.1";
|
|
|
31
31
|
var PLUGINS = [
|
|
32
32
|
{
|
|
33
33
|
id: "customers",
|
|
34
|
-
name: "
|
|
35
|
-
hint: "
|
|
34
|
+
name: "Customer Accounts",
|
|
35
|
+
hint: "customer accounts + login (identity for commerce)",
|
|
36
36
|
pkg: "@saena-io/customers",
|
|
37
37
|
version: SAENA_VERSION,
|
|
38
38
|
dependsOn: [],
|
|
@@ -41,8 +41,8 @@ var PLUGINS = [
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
id: "ai",
|
|
44
|
-
name: "AI",
|
|
45
|
-
hint: "
|
|
44
|
+
name: "AI Secrets",
|
|
45
|
+
hint: "encrypted storage for AI provider keys, shared by AI features",
|
|
46
46
|
pkg: "@saena-io/ai",
|
|
47
47
|
version: SAENA_VERSION,
|
|
48
48
|
dependsOn: [],
|
|
@@ -54,8 +54,8 @@ var PLUGINS = [
|
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
id: "translation",
|
|
57
|
-
name: "
|
|
58
|
-
hint: "
|
|
57
|
+
name: "AI Translate",
|
|
58
|
+
hint: "machine-translate your content (needs AI Secrets)",
|
|
59
59
|
pkg: "@saena-io/translation",
|
|
60
60
|
version: SAENA_VERSION,
|
|
61
61
|
dependsOn: ["ai"],
|
|
@@ -65,8 +65,8 @@ var PLUGINS = [
|
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
id: "content",
|
|
68
|
-
name: "
|
|
69
|
-
hint: "
|
|
68
|
+
name: "CMS",
|
|
69
|
+
hint: "client-editable pages, sections & collections",
|
|
70
70
|
pkg: "@saena-io/content",
|
|
71
71
|
version: SAENA_VERSION,
|
|
72
72
|
dependsOn: [],
|
|
@@ -212,7 +212,7 @@ async function main() {
|
|
|
212
212
|
selectedIds = ["content", "ai", "translation"];
|
|
213
213
|
} else {
|
|
214
214
|
const res = await multiselect({
|
|
215
|
-
message: "Which plugins? (Core already includes auth, RBAC, settings, the translations grid, and Help)",
|
|
215
|
+
message: "Which plugins? Space toggles, enter confirms \u2014 or select none for a core-only app. (Core already includes auth, RBAC, settings, the translations grid, and Help.)",
|
|
216
216
|
options: PLUGINS.map((p) => ({ value: p.id, label: p.name, hint: p.hint })),
|
|
217
217
|
required: false,
|
|
218
218
|
initialValues: ["content", "ai", "translation"]
|
package/package.json
CHANGED
|
@@ -8,23 +8,65 @@
|
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"start": "vite preview",
|
|
10
10
|
"typecheck": "tsc --noEmit",
|
|
11
|
+
"ui:update": "bun scripts/ui-update.ts",
|
|
11
12
|
"db:migrate": "bun --env-file=.env scripts/migrate.ts",
|
|
12
13
|
"db:migrate:deploy": "bun scripts/migrate.ts",
|
|
13
14
|
"db:seed": "bun --env-file=.env scripts/seed-staff.ts"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
17
|
+
"@ai-sdk/react": "^4.0.2",
|
|
18
|
+
"@base-ui/react": "^1.5.0",
|
|
19
|
+
"@fontsource-variable/noto-sans": "^5.2.10",
|
|
20
|
+
"@hugeicons/core-free-icons": "^4.2.0",
|
|
21
|
+
"@hugeicons/react": "^1.1.6",
|
|
22
|
+
"@platejs/ai": "^53.2.2",
|
|
23
|
+
"@platejs/basic-nodes": "^53.0.0",
|
|
24
|
+
"@platejs/basic-styles": "^53.0.0",
|
|
25
|
+
"@platejs/code-block": "^53.0.0",
|
|
26
|
+
"@platejs/dnd": "^53.1.0",
|
|
27
|
+
"@platejs/docx": "^53.0.0",
|
|
28
|
+
"@platejs/floating": "^53.0.0",
|
|
29
|
+
"@platejs/indent": "^53.0.0",
|
|
30
|
+
"@platejs/juice": "^53.0.0",
|
|
31
|
+
"@platejs/link": "^53.0.3",
|
|
32
|
+
"@platejs/list": "^53.1.3",
|
|
33
|
+
"@platejs/markdown": "^53.2.2",
|
|
34
|
+
"@platejs/resizable": "^53.0.0",
|
|
35
|
+
"@platejs/selection": "^53.1.6",
|
|
36
|
+
"@platejs/table": "^53.0.0",
|
|
37
|
+
"@platejs/toggle": "^53.0.0",
|
|
16
38
|
"@saena-io/admin": "^0.1.1",
|
|
17
39
|
"@saena-io/catalog": "^0.1.0",
|
|
18
40
|
"@saena-io/core": "^0.1.1",
|
|
19
41
|
"@saena-io/plugin-sdk": "^0.1.0",
|
|
20
|
-
"@saena-io/ui": "^0.1.
|
|
42
|
+
"@saena-io/ui": "^0.1.2",
|
|
21
43
|
"@tanstack/db": "^0.6.8",
|
|
22
44
|
"@tanstack/query-core": "^5.101.0",
|
|
23
45
|
"@tanstack/query-db-collection": "^1.0.40",
|
|
24
46
|
"@tanstack/react-router": "^1.170.0",
|
|
25
47
|
"@tanstack/react-start": "^1.168.0",
|
|
48
|
+
"ai": "^7.0.2",
|
|
49
|
+
"class-variance-authority": "^0.7.1",
|
|
50
|
+
"clsx": "^2.1.1",
|
|
51
|
+
"cmdk": "^1.1.1",
|
|
52
|
+
"embla-carousel-react": "^8.6.0",
|
|
53
|
+
"input-otp": "^1.4.2",
|
|
54
|
+
"lowlight": "^3.3.0",
|
|
55
|
+
"next-themes": "^0.4.6",
|
|
56
|
+
"platejs": "^53.2.1",
|
|
26
57
|
"react": "^19.2.6",
|
|
27
|
-
"react-
|
|
58
|
+
"react-day-picker": "^10.0.1",
|
|
59
|
+
"react-dnd": "^16.0.1",
|
|
60
|
+
"react-dnd-html5-backend": "^16.0.1",
|
|
61
|
+
"react-dom": "^19.2.6",
|
|
62
|
+
"react-resizable-panels": "^4.11.2",
|
|
63
|
+
"recharts": "3.8.0",
|
|
64
|
+
"remark-gfm": "^4.0.1",
|
|
65
|
+
"shadcn": "^4.11.0",
|
|
66
|
+
"sonner": "^2.0.7",
|
|
67
|
+
"tailwind-merge": "^3.6.0",
|
|
68
|
+
"tw-animate-css": "^1.4.0",
|
|
69
|
+
"vaul": "^1.1.2"
|
|
28
70
|
},
|
|
29
71
|
"devDependencies": {
|
|
30
72
|
"@tailwindcss/vite": "^4",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, relative } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// Re-fetch ejected UI components from the installed @saena-io/ui into src/components/ui (plus src/lib
|
|
6
|
+
// and src/hooks). You OWN these files, so this OVERWRITES the named one(s) with the package's current
|
|
7
|
+
// version — review with `git diff` afterwards and re-apply any edits you'd made. (globals.css is NOT
|
|
8
|
+
// touched here: it carries your own @source/theme customizations.)
|
|
9
|
+
//
|
|
10
|
+
// bun run ui:update button # one component
|
|
11
|
+
// bun run ui:update rich-text/toolbar # a nested one
|
|
12
|
+
// bun run ui:update --all # re-sync everything you've ejected
|
|
13
|
+
|
|
14
|
+
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
|
15
|
+
const pkgRoot = join(appRoot, 'node_modules', '@saena-io', 'ui', 'src');
|
|
16
|
+
const localRoot = join(appRoot, 'src');
|
|
17
|
+
const uiDir = join(localRoot, 'components', 'ui');
|
|
18
|
+
|
|
19
|
+
if (!existsSync(pkgRoot)) {
|
|
20
|
+
console.error('@saena-io/ui source not found — run `bun install` first.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Map a local file (under src/) to its @saena-io/ui source counterpart, or null if it isn't an ejected one. */
|
|
25
|
+
function pkgPathFor(localPath: string): string | null {
|
|
26
|
+
const rel = relative(localRoot, localPath).replace(/\\/g, '/');
|
|
27
|
+
if (rel.startsWith('components/ui/'))
|
|
28
|
+
return join(pkgRoot, 'components', rel.slice('components/ui/'.length));
|
|
29
|
+
if (rel.startsWith('lib/') || rel.startsWith('hooks/')) return join(pkgRoot, rel);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function update(localPath: string): boolean {
|
|
34
|
+
const pkgPath = pkgPathFor(localPath);
|
|
35
|
+
if (!pkgPath || !existsSync(pkgPath)) return false;
|
|
36
|
+
mkdirSync(dirname(localPath), { recursive: true });
|
|
37
|
+
copyFileSync(pkgPath, localPath);
|
|
38
|
+
console.log(` updated ${relative(appRoot, localPath).replace(/\\/g, '/')}`);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function walk(dir: string, out: string[] = []): string[] {
|
|
43
|
+
if (!existsSync(dir)) return out;
|
|
44
|
+
for (const entry of readdirSync(dir)) {
|
|
45
|
+
const full = join(dir, entry);
|
|
46
|
+
if (statSync(full).isDirectory()) walk(full, out);
|
|
47
|
+
else if (/\.(ts|tsx)$/.test(entry)) out.push(full);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const arg = process.argv[2];
|
|
53
|
+
if (!arg) {
|
|
54
|
+
console.log('Re-fetch ejected UI from @saena-io/ui.\n');
|
|
55
|
+
console.log(' bun run ui:update <component> e.g. button · card · rich-text/toolbar');
|
|
56
|
+
console.log(' bun run ui:update --all re-sync everything you have ejected');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let count = 0;
|
|
61
|
+
if (arg === '--all') {
|
|
62
|
+
console.log('Re-fetching all ejected UI from @saena-io/ui…');
|
|
63
|
+
const targets = walk(uiDir);
|
|
64
|
+
const utils = join(localRoot, 'lib', 'utils.ts');
|
|
65
|
+
if (existsSync(utils)) targets.push(utils);
|
|
66
|
+
targets.push(...walk(join(localRoot, 'hooks')));
|
|
67
|
+
for (const t of targets) if (update(t)) count++;
|
|
68
|
+
} else {
|
|
69
|
+
const name = arg.replace(/\.tsx?$/, '');
|
|
70
|
+
const ext = existsSync(join(pkgRoot, 'components', `${name}.tsx`))
|
|
71
|
+
? 'tsx'
|
|
72
|
+
: existsSync(join(pkgRoot, 'components', `${name}.ts`))
|
|
73
|
+
? 'ts'
|
|
74
|
+
: null;
|
|
75
|
+
if (!ext) {
|
|
76
|
+
console.error(`No component "${name}" in @saena-io/ui.`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
if (update(join(uiDir, `${name}.${ext}`))) count++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(`\nDone — ${count} file(s) updated. Review with \`git diff\` and re-apply any local edits.`);
|
|
83
|
+
process.exit(0);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';
|
|
2
|
+
|
|
3
|
+
import { ArrowDown01Icon, ArrowUp01Icon } from '@hugeicons/core-free-icons';
|
|
4
|
+
import { HugeiconsIcon } from '@hugeicons/react';
|
|
5
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
6
|
+
|
|
7
|
+
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<AccordionPrimitive.Root
|
|
10
|
+
data-slot="accordion"
|
|
11
|
+
className={cn('flex w-full flex-col overflow-hidden rounded-md border', className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
18
|
+
return (
|
|
19
|
+
<AccordionPrimitive.Item
|
|
20
|
+
data-slot="accordion-item"
|
|
21
|
+
className={cn('not-last:border-b data-open:bg-muted/50', className)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {
|
|
28
|
+
return (
|
|
29
|
+
<AccordionPrimitive.Header className="flex">
|
|
30
|
+
<AccordionPrimitive.Trigger
|
|
31
|
+
data-slot="accordion-trigger"
|
|
32
|
+
className={cn(
|
|
33
|
+
'group/accordion-trigger relative flex flex-1 items-start justify-between gap-6 border border-transparent p-2 text-left text-xs/relaxed font-medium transition-all outline-none hover:underline aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
<HugeiconsIcon
|
|
40
|
+
icon={ArrowDown01Icon}
|
|
41
|
+
strokeWidth={2}
|
|
42
|
+
data-slot="accordion-trigger-icon"
|
|
43
|
+
className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
|
|
44
|
+
/>
|
|
45
|
+
<HugeiconsIcon
|
|
46
|
+
icon={ArrowUp01Icon}
|
|
47
|
+
strokeWidth={2}
|
|
48
|
+
data-slot="accordion-trigger-icon"
|
|
49
|
+
className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
|
|
50
|
+
/>
|
|
51
|
+
</AccordionPrimitive.Trigger>
|
|
52
|
+
</AccordionPrimitive.Header>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props) {
|
|
57
|
+
return (
|
|
58
|
+
<AccordionPrimitive.Panel
|
|
59
|
+
data-slot="accordion-content"
|
|
60
|
+
className="overflow-hidden px-2 text-xs/relaxed data-open:animate-accordion-down data-closed:animate-accordion-up"
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
className={cn(
|
|
65
|
+
'h-(--accordion-panel-height) pt-0 pb-4 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
</AccordionPrimitive.Panel>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';
|
|
4
|
+
import type * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import { Button } from '@saena-io/ui/components/button';
|
|
7
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
8
|
+
|
|
9
|
+
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
|
|
10
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
|
|
14
|
+
return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
|
|
18
|
+
return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
|
|
22
|
+
return (
|
|
23
|
+
<AlertDialogPrimitive.Backdrop
|
|
24
|
+
data-slot="alert-dialog-overlay"
|
|
25
|
+
className={cn(
|
|
26
|
+
'fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function AlertDialogContent({
|
|
35
|
+
className,
|
|
36
|
+
size = 'default',
|
|
37
|
+
...props
|
|
38
|
+
}: AlertDialogPrimitive.Popup.Props & {
|
|
39
|
+
size?: 'default' | 'sm';
|
|
40
|
+
}) {
|
|
41
|
+
return (
|
|
42
|
+
<AlertDialogPortal>
|
|
43
|
+
<AlertDialogOverlay />
|
|
44
|
+
<AlertDialogPrimitive.Popup
|
|
45
|
+
data-slot="alert-dialog-content"
|
|
46
|
+
data-size={size}
|
|
47
|
+
className={cn(
|
|
48
|
+
'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
</AlertDialogPortal>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
data-slot="alert-dialog-header"
|
|
61
|
+
className={cn(
|
|
62
|
+
'grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
data-slot="alert-dialog-footer"
|
|
74
|
+
className={cn(
|
|
75
|
+
'flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function AlertDialogMedia({ className, ...props }: React.ComponentProps<'div'>) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-slot="alert-dialog-media"
|
|
87
|
+
className={cn(
|
|
88
|
+
"mb-2 inline-flex size-8 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4",
|
|
89
|
+
className,
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function AlertDialogTitle({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
100
|
+
return (
|
|
101
|
+
<AlertDialogPrimitive.Title
|
|
102
|
+
data-slot="alert-dialog-title"
|
|
103
|
+
className={cn(
|
|
104
|
+
'font-heading text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function AlertDialogDescription({
|
|
113
|
+
className,
|
|
114
|
+
...props
|
|
115
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
116
|
+
return (
|
|
117
|
+
<AlertDialogPrimitive.Description
|
|
118
|
+
data-slot="alert-dialog-description"
|
|
119
|
+
className={cn(
|
|
120
|
+
'text-xs/relaxed text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
|
|
121
|
+
className,
|
|
122
|
+
)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof Button>) {
|
|
129
|
+
return <Button data-slot="alert-dialog-action" className={cn(className)} {...props} />;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function AlertDialogCancel({
|
|
133
|
+
className,
|
|
134
|
+
variant = 'outline',
|
|
135
|
+
size = 'default',
|
|
136
|
+
...props
|
|
137
|
+
}: AlertDialogPrimitive.Close.Props &
|
|
138
|
+
Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {
|
|
139
|
+
return (
|
|
140
|
+
<AlertDialogPrimitive.Close
|
|
141
|
+
data-slot="alert-dialog-cancel"
|
|
142
|
+
className={cn(className)}
|
|
143
|
+
render={<Button variant={variant} size={size} />}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
AlertDialog,
|
|
151
|
+
AlertDialogAction,
|
|
152
|
+
AlertDialogCancel,
|
|
153
|
+
AlertDialogContent,
|
|
154
|
+
AlertDialogDescription,
|
|
155
|
+
AlertDialogFooter,
|
|
156
|
+
AlertDialogHeader,
|
|
157
|
+
AlertDialogMedia,
|
|
158
|
+
AlertDialogOverlay,
|
|
159
|
+
AlertDialogPortal,
|
|
160
|
+
AlertDialogTitle,
|
|
161
|
+
AlertDialogTrigger,
|
|
162
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type VariantProps, cva } from 'class-variance-authority';
|
|
2
|
+
import type * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-card text-card-foreground',
|
|
12
|
+
destructive:
|
|
13
|
+
'bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: 'default',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
function Alert({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-slot="alert"
|
|
30
|
+
role="alert"
|
|
31
|
+
className={cn(alertVariants({ variant }), className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="alert-title"
|
|
41
|
+
className={cn(
|
|
42
|
+
'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground',
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="alert-description"
|
|
54
|
+
className={cn(
|
|
55
|
+
'text-xs/relaxed text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function AlertAction({ className, ...props }: React.ComponentProps<'div'>) {
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
data-slot="alert-action"
|
|
67
|
+
className={cn('absolute top-1.5 right-2', className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { Alert, AlertTitle, AlertDescription, AlertAction };
|