@indinno/digicode-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DigiCode
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,167 @@
1
+ # DigiCode
2
+
3
+ A source-owned React UI primitive library with a shadcn-style CLI. It contains **UI elements only** — no pages, marketing sections, headers, footers, dashboards, or application-specific content.
4
+
5
+ The visual language is based on the clean DigiCode UI: dark ink actions, blue accents, soft surfaces, 12–18px radii, restrained borders, and accessible focus states.
6
+
7
+ ## Components
8
+
9
+ - Accordion
10
+ - Alert
11
+ - Avatar
12
+ - Badge
13
+ - Breadcrumb
14
+ - Button
15
+ - Card
16
+ - Checkbox
17
+ - Dialog
18
+ - Drawer
19
+ - Dropdown Menu
20
+ - Empty State
21
+ - Input
22
+ - Pagination
23
+ - Progress
24
+ - Radio Group
25
+ - Select
26
+ - Separator
27
+ - Skeleton
28
+ - Spinner
29
+ - Stat Card
30
+ - Switch
31
+ - Table
32
+ - Tabs
33
+ - Textarea
34
+ - Tooltip
35
+
36
+ ## Publish to npm
37
+
38
+ 1. Change the package name in `package.json` if needed. The starter uses:
39
+
40
+ ```json
41
+ "name": "@indinno/digicode-ui"
42
+ ```
43
+
44
+ 2. Login and publish:
45
+
46
+ ```bash
47
+ npm login
48
+ npm publish --access public
49
+ ```
50
+
51
+ ## Use it like shadcn
52
+
53
+ Initialize the target React/Next.js project:
54
+
55
+ ```bash
56
+ npx @indinno/digicode-ui init
57
+ ```
58
+
59
+ Add only the components you need:
60
+
61
+ ```bash
62
+ npx @indinno/digicode-ui add button input card badge
63
+ ```
64
+
65
+ Or install the full primitive set:
66
+
67
+ ```bash
68
+ npx @indinno/digicode-ui add all
69
+ ```
70
+
71
+ List available components:
72
+
73
+ ```bash
74
+ npx @indinno/digicode-ui list
75
+ ```
76
+
77
+ The CLI copies source into either:
78
+
79
+ ```text
80
+ src/components/ui
81
+ src/lib/cn.ts
82
+ src/styles/digicode.css
83
+ ```
84
+
85
+ or, for projects without a `src` directory:
86
+
87
+ ```text
88
+ components/ui
89
+ lib/cn.ts
90
+ styles/digicode.css
91
+ ```
92
+
93
+ That means the consuming project **owns the source code**, just like the shadcn model. You can edit a copied button or card locally without waiting for a library release.
94
+
95
+ ## Import the stylesheet once
96
+
97
+ For a Next.js app using `src/app`, add this to the root layout or global stylesheet entry:
98
+
99
+ ```tsx
100
+ import "../styles/digicode.css";
101
+ ```
102
+
103
+ Then use copied components normally:
104
+
105
+ ```tsx
106
+ import { Button } from "@/components/ui/button";
107
+ import {
108
+ Card,
109
+ CardContent,
110
+ CardHeader,
111
+ CardTitle,
112
+ } from "@/components/ui/card";
113
+ import { Input } from "@/components/ui/input";
114
+
115
+ export function Example() {
116
+ return (
117
+ <Card elevated>
118
+ <CardHeader>
119
+ <CardTitle>Client details</CardTitle>
120
+ </CardHeader>
121
+ <CardContent>
122
+ <Input label="Client name" placeholder="Enter client name" />
123
+ <Button>Save client</Button>
124
+ </CardContent>
125
+ </Card>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ## Dark theme
131
+
132
+ Set `data-digicode-theme="dark"` on any parent to switch the token set:
133
+
134
+ ```tsx
135
+ <div data-digicode-theme="dark">
136
+ <Button>Dark theme button</Button>
137
+ </div>
138
+ ```
139
+
140
+ ## Customize the design system
141
+
142
+ Change the CSS variables at the top of `styles/digicode.css`:
143
+
144
+ ```css
145
+ :root {
146
+ --digicode-primary: #2474ad;
147
+ --digicode-ink: #1c2f41;
148
+ --digicode-radius: 12px;
149
+ --digicode-radius-lg: 18px;
150
+ }
151
+ ```
152
+
153
+ Because the files are copied into the project, you can also modify each component directly.
154
+
155
+ ## Package philosophy
156
+
157
+ This package intentionally excludes:
158
+
159
+ - complete pages
160
+ - hero sections
161
+ - website navigation
162
+ - footer sections
163
+ - DigiCode-specific copy/data
164
+ - API/enquiry logic
165
+ - app-specific dashboards
166
+
167
+ Only reusable UI primitives belong here.
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const registryRoot = path.join(packageRoot, "registry");
10
+ const manifest = JSON.parse(fs.readFileSync(path.join(registryRoot, "registry.json"), "utf8"));
11
+ const args = process.argv.slice(2);
12
+ const command = args[0];
13
+ const requested = args.slice(1);
14
+ const cwd = process.cwd();
15
+
16
+ function fail(message) {
17
+ console.error(`\nDigiCode: ${message}\n`);
18
+ process.exit(1);
19
+ }
20
+
21
+ function detectSourceRoot() {
22
+ if (fs.existsSync(path.join(cwd, "src"))) return path.join(cwd, "src");
23
+ return cwd;
24
+ }
25
+
26
+ function paths() {
27
+ const sourceRoot = detectSourceRoot();
28
+ return {
29
+ sourceRoot,
30
+ componentsDir: path.join(sourceRoot, "components", "ui"),
31
+ libDir: path.join(sourceRoot, "lib"),
32
+ stylesDir: path.join(sourceRoot, "styles"),
33
+ configFile: path.join(cwd, "digicode.json")
34
+ };
35
+ }
36
+
37
+ function ensureDir(dir) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+
41
+ function copyIfMissing(source, destination, force = false) {
42
+ ensureDir(path.dirname(destination));
43
+ if (fs.existsSync(destination) && !force) return false;
44
+ fs.copyFileSync(source, destination);
45
+ return true;
46
+ }
47
+
48
+ function init() {
49
+ const p = paths();
50
+ ensureDir(p.componentsDir);
51
+ ensureDir(p.libDir);
52
+ ensureDir(p.stylesDir);
53
+
54
+ copyIfMissing(path.join(registryRoot, "lib", "cn.ts"), path.join(p.libDir, "cn.ts"));
55
+ copyIfMissing(path.join(registryRoot, "styles", "digicode.css"), path.join(p.stylesDir, "digicode.css"));
56
+
57
+ if (!fs.existsSync(p.configFile)) {
58
+ fs.writeFileSync(
59
+ p.configFile,
60
+ JSON.stringify(
61
+ {
62
+ sourceRoot: path.relative(cwd, p.sourceRoot) || ".",
63
+ components: path.relative(cwd, p.componentsDir),
64
+ styles: path.relative(cwd, path.join(p.stylesDir, "digicode.css")),
65
+ style: manifest.style
66
+ },
67
+ null,
68
+ 2
69
+ ) + "\n"
70
+ );
71
+ }
72
+
73
+ console.log("\nDigiCode initialized.");
74
+ console.log(`Components: ${path.relative(cwd, p.componentsDir)}`);
75
+ console.log(`Styles: ${path.relative(cwd, path.join(p.stylesDir, "digicode.css"))}`);
76
+ console.log("\nImport the stylesheet once in your root layout/main entry.");
77
+ if (p.sourceRoot.endsWith(`${path.sep}src`)) {
78
+ console.log('For Next.js src/app/layout.tsx: import "../styles/digicode.css";');
79
+ } else {
80
+ console.log('For app/layout.tsx: import "../styles/digicode.css";');
81
+ }
82
+ }
83
+
84
+ function add(names) {
85
+ if (!names.length) fail(`Specify components, for example: digicode add button input card`);
86
+ init();
87
+
88
+ const p = paths();
89
+ const targets = names.includes("all") ? manifest.components : names;
90
+ const unknown = targets.filter((name) => !manifest.components.includes(name));
91
+ if (unknown.length) fail(`Unknown component${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}`);
92
+
93
+ let copied = 0;
94
+ for (const name of targets) {
95
+ const source = path.join(registryRoot, "components", `${name}.tsx`);
96
+ const destination = path.join(p.componentsDir, `${name}.tsx`);
97
+ if (copyIfMissing(source, destination)) {
98
+ console.log(`+ ${path.relative(cwd, destination)}`);
99
+ copied++;
100
+ } else {
101
+ console.log(`= ${path.relative(cwd, destination)} (already exists)`);
102
+ }
103
+ }
104
+
105
+ console.log(`\n${copied} component${copied === 1 ? "" : "s"} added.`);
106
+ }
107
+
108
+ function list() {
109
+ console.log("\nAvailable DigiCode components:\n");
110
+ for (const name of manifest.components) console.log(` - ${name}`);
111
+ console.log("");
112
+ }
113
+
114
+ function help() {
115
+ console.log(`\nDigiCode CLI\n\nUsage:\n digicode init\n digicode add button input card\n digicode add all\n digicode list\n\nWith npx after publishing:\n npx @indinno/digicode-ui init\n npx @indinno/digicode-ui add button input card\n`);
116
+ }
117
+
118
+ switch (command) {
119
+ case "init":
120
+ init();
121
+ break;
122
+ case "add":
123
+ add(requested);
124
+ break;
125
+ case "list":
126
+ list();
127
+ break;
128
+ case undefined:
129
+ case "help":
130
+ case "--help":
131
+ case "-h":
132
+ help();
133
+ break;
134
+ default:
135
+ fail(`Unknown command: ${command}`);
136
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@indinno/digicode-ui",
3
+ "version": "0.1.0",
4
+ "description": "DigiCode shadcn-style source-owned React UI primitives for reusable product interfaces.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "digicode": "./bin/digicode.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "registry",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "keywords": [
17
+ "react",
18
+ "nextjs",
19
+ "ui",
20
+ "components",
21
+ "design-system",
22
+ "shadcn",
23
+ "cli"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ import { useState, type ReactNode } from "react";
4
+
5
+ export interface AccordionItem {
6
+ value: string;
7
+ title: ReactNode;
8
+ content: ReactNode;
9
+ }
10
+
11
+ export interface AccordionProps {
12
+ items: AccordionItem[];
13
+ defaultValue?: string;
14
+ allowCollapse?: boolean;
15
+ className?: string;
16
+ }
17
+
18
+ export function Accordion({ items, defaultValue, allowCollapse = true, className }: AccordionProps) {
19
+ const [open, setOpen] = useState(defaultValue ?? "");
20
+ return (
21
+ <div className={className ? `digicode-accordion ${className}` : "digicode-accordion"}>
22
+ {items.map((item) => {
23
+ const expanded = open === item.value;
24
+ return (
25
+ <div className="digicode-accordion__item" key={item.value}>
26
+ <button
27
+ type="button"
28
+ className="digicode-accordion__trigger"
29
+ aria-expanded={expanded}
30
+ onClick={() => setOpen(expanded && allowCollapse ? "" : item.value)}
31
+ >
32
+ <span>{item.title}</span>
33
+ <span className="digicode-accordion__chevron" aria-hidden="true">⌄</span>
34
+ </button>
35
+ {expanded && <div className="digicode-accordion__content">{item.content}</div>}
36
+ </div>
37
+ );
38
+ })}
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,21 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export type AlertVariant = "default" | "info" | "success" | "warning" | "danger";
5
+ export interface AlertProps extends HTMLAttributes<HTMLDivElement> {
6
+ variant?: AlertVariant;
7
+ icon?: ReactNode;
8
+ title?: string;
9
+ }
10
+
11
+ export function Alert({ className, variant = "default", icon, title, children, ...props }: AlertProps) {
12
+ return (
13
+ <div role="alert" className={cn("digicode-alert", variant !== "default" && `digicode-alert--${variant}`, className)} {...props}>
14
+ {icon && <span className="digicode-alert__icon">{icon}</span>}
15
+ <div>
16
+ {title && <p className="digicode-alert__title">{title}</p>}
17
+ <div className="digicode-alert__description">{children}</div>
18
+ </div>
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,18 @@
1
+ import type { HTMLAttributes, ImgHTMLAttributes } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export interface AvatarProps extends HTMLAttributes<HTMLSpanElement> {
5
+ src?: string;
6
+ alt?: string;
7
+ fallback?: string;
8
+ size?: "sm" | "md" | "lg";
9
+ imageProps?: ImgHTMLAttributes<HTMLImageElement>;
10
+ }
11
+
12
+ export function Avatar({ className, src, alt = "", fallback = "?", size = "md", imageProps, ...props }: AvatarProps) {
13
+ return (
14
+ <span className={cn("digicode-avatar", `digicode-avatar--${size}`, className)} {...props}>
15
+ {src ? <img src={src} alt={alt} {...imageProps} /> : fallback}
16
+ </span>
17
+ );
18
+ }
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export type BadgeVariant = "default" | "primary" | "success" | "warning" | "danger";
5
+ export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> { variant?: BadgeVariant; }
6
+
7
+ export function Badge({ className, variant = "default", ...props }: BadgeProps) {
8
+ return <span className={cn("digicode-badge", variant !== "default" && `digicode-badge--${variant}`, className)} {...props} />;
9
+ }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export interface BreadcrumbItem {
4
+ label: ReactNode;
5
+ href?: string;
6
+ }
7
+
8
+ export function Breadcrumb({ items, separator = "/", className }: { items: BreadcrumbItem[]; separator?: ReactNode; className?: string }) {
9
+ return (
10
+ <nav aria-label="Breadcrumb" className={className ? `digicode-breadcrumb ${className}` : "digicode-breadcrumb"}>
11
+ {items.map((item, index) => {
12
+ const current = index === items.length - 1;
13
+ return (
14
+ <span key={index} style={{ display: "contents" }}>
15
+ {current ? <span className="digicode-breadcrumb__current" aria-current="page">{item.label}</span> : item.href ? <a href={item.href}>{item.label}</a> : <span>{item.label}</span>}
16
+ {!current && <span className="digicode-breadcrumb__sep" aria-hidden="true">{separator}</span>}
17
+ </span>
18
+ );
19
+ })}
20
+ </nav>
21
+ );
22
+ }
@@ -0,0 +1,38 @@
1
+ import type { ButtonHTMLAttributes, ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "destructive" | "link";
5
+ export type ButtonSize = "sm" | "md" | "lg" | "icon";
6
+
7
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
8
+ variant?: ButtonVariant;
9
+ size?: ButtonSize;
10
+ loading?: boolean;
11
+ leftIcon?: ReactNode;
12
+ rightIcon?: ReactNode;
13
+ }
14
+
15
+ export function Button({
16
+ className,
17
+ variant = "primary",
18
+ size = "md",
19
+ loading = false,
20
+ leftIcon,
21
+ rightIcon,
22
+ disabled,
23
+ children,
24
+ ...props
25
+ }: ButtonProps) {
26
+ return (
27
+ <button
28
+ className={cn("digicode-button", `digicode-button--${variant}`, size !== "md" && `digicode-button--${size}`, className)}
29
+ disabled={disabled || loading}
30
+ aria-busy={loading || undefined}
31
+ {...props}
32
+ >
33
+ {loading ? <span className="digicode-button__spinner" aria-hidden="true" /> : leftIcon}
34
+ {children}
35
+ {!loading && rightIcon}
36
+ </button>
37
+ );
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export interface CardProps extends HTMLAttributes<HTMLDivElement> { elevated?: boolean; }
5
+ export function Card({ className, elevated = false, ...props }: CardProps) { return <div className={cn("digicode-card", elevated && "digicode-card--elevated", className)} {...props} />; }
6
+ export function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("digicode-card__header", className)} {...props} />; }
7
+ export function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) { return <h3 className={cn("digicode-card__title", className)} {...props} />; }
8
+ export function CardDescription({ className, ...props }: HTMLAttributes<HTMLParagraphElement>) { return <p className={cn("digicode-card__description", className)} {...props} />; }
9
+ export function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("digicode-card__content", className)} {...props} />; }
10
+ export function CardFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) { return <div className={cn("digicode-card__footer", className)} {...props} />; }
@@ -0,0 +1,15 @@
1
+ import { forwardRef, type InputHTMLAttributes, type ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
5
+ label?: ReactNode;
6
+ }
7
+
8
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox({ className, label, ...props }, ref) {
9
+ return (
10
+ <label className="digicode-checkline">
11
+ <input ref={ref} type="checkbox" className={cn("digicode-checkbox", className)} {...props} />
12
+ {label && <span>{label}</span>}
13
+ </label>
14
+ );
15
+ });
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, type ReactNode } from "react";
4
+
5
+ export interface DialogProps {
6
+ open: boolean;
7
+ onOpenChange: (open: boolean) => void;
8
+ title: ReactNode;
9
+ description?: ReactNode;
10
+ children?: ReactNode;
11
+ footer?: ReactNode;
12
+ className?: string;
13
+ }
14
+
15
+ export function Dialog({ open, onOpenChange, title, description, children, footer, className }: DialogProps) {
16
+ const ref = useRef<HTMLDialogElement>(null);
17
+
18
+ useEffect(() => {
19
+ const node = ref.current;
20
+ if (!node) return;
21
+ if (open && !node.open) node.showModal();
22
+ if (!open && node.open) node.close();
23
+ }, [open]);
24
+
25
+ return (
26
+ <dialog
27
+ ref={ref}
28
+ className={className ? `digicode-dialog-native ${className}` : "digicode-dialog-native"}
29
+ onClose={() => onOpenChange(false)}
30
+ onCancel={(event) => {
31
+ event.preventDefault();
32
+ onOpenChange(false);
33
+ }}
34
+ >
35
+ <div className="digicode-dialog__header">
36
+ <div>
37
+ <h2 className="digicode-dialog__title">{title}</h2>
38
+ {description && <p className="digicode-dialog__description">{description}</p>}
39
+ </div>
40
+ <button type="button" className="digicode-dialog__close" aria-label="Close dialog" onClick={() => onOpenChange(false)}>×</button>
41
+ </div>
42
+ {children && <div className="digicode-dialog__body">{children}</div>}
43
+ {footer && <div className="digicode-dialog__footer">{footer}</div>}
44
+ </dialog>
45
+ );
46
+ }
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, type ReactNode } from "react";
4
+
5
+ export interface DrawerProps {
6
+ open: boolean;
7
+ onOpenChange: (open: boolean) => void;
8
+ title: ReactNode;
9
+ children?: ReactNode;
10
+ footer?: ReactNode;
11
+ className?: string;
12
+ }
13
+
14
+ export function Drawer({ open, onOpenChange, title, children, footer, className }: DrawerProps) {
15
+ const ref = useRef<HTMLDialogElement>(null);
16
+
17
+ useEffect(() => {
18
+ const node = ref.current;
19
+ if (!node) return;
20
+ if (open && !node.open) node.showModal();
21
+ if (!open && node.open) node.close();
22
+ }, [open]);
23
+
24
+ return (
25
+ <dialog
26
+ ref={ref}
27
+ className={className ? `digicode-drawer-native ${className}` : "digicode-drawer-native"}
28
+ onClose={() => onOpenChange(false)}
29
+ onCancel={(event) => {
30
+ event.preventDefault();
31
+ onOpenChange(false);
32
+ }}
33
+ >
34
+ <div className="digicode-drawer__header">
35
+ <h2 className="digicode-dialog__title">{title}</h2>
36
+ <button type="button" className="digicode-dialog__close" aria-label="Close drawer" onClick={() => onOpenChange(false)}>×</button>
37
+ </div>
38
+ {children && <div className="digicode-drawer__body">{children}</div>}
39
+ {footer && <div className="digicode-drawer__footer">{footer}</div>}
40
+ </dialog>
41
+ );
42
+ }
@@ -0,0 +1,38 @@
1
+ import type { ButtonHTMLAttributes, DetailsHTMLAttributes, ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export interface DropdownMenuProps extends DetailsHTMLAttributes<HTMLDetailsElement> {
5
+ trigger: ReactNode;
6
+ children: ReactNode;
7
+ }
8
+
9
+ export function DropdownMenu({ className, trigger, children, ...props }: DropdownMenuProps) {
10
+ return (
11
+ <details className={cn("digicode-dropdown", className)} {...props}>
12
+ <summary>{trigger}</summary>
13
+ <div className="digicode-dropdown__menu">{children}</div>
14
+ </details>
15
+ );
16
+ }
17
+
18
+ export function DropdownMenuLabel({ children }: { children: ReactNode }) {
19
+ return <div className="digicode-dropdown__label">{children}</div>;
20
+ }
21
+
22
+ export function DropdownMenuSeparator() {
23
+ return <div className="digicode-dropdown__separator" role="separator" />;
24
+ }
25
+
26
+ export interface DropdownMenuItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
27
+ danger?: boolean;
28
+ icon?: ReactNode;
29
+ }
30
+
31
+ export function DropdownMenuItem({ className, danger = false, icon, children, ...props }: DropdownMenuItemProps) {
32
+ return (
33
+ <button type="button" className={cn("digicode-dropdown__item", danger && "digicode-dropdown__item--danger", className)} {...props}>
34
+ {icon}
35
+ <span>{children}</span>
36
+ </button>
37
+ );
38
+ }