aback-ui 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +85 -2
- package/dist/args.d.ts +15 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +91 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +29 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +82 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +85 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +45 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +19 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +97 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +11 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +42 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/view.d.ts +11 -0
- package/dist/commands/view.d.ts.map +1 -0
- package/dist/commands/view.js +43 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +265 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +30 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +121 -0
- package/dist/diff.js.map +1 -0
- package/dist/doctor.d.ts +35 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +203 -0
- package/dist/doctor.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/help.d.ts +3 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +48 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +53 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +119 -0
- package/dist/install.js.map +1 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +47 -0
- package/dist/logger.js.map +1 -0
- package/dist/paths.d.ts +18 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +76 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompt.d.ts +13 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +25 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +49 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +216 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -4
- package/registry/accordion.json +32 -0
- package/registry/alert-dialog.json +28 -0
- package/registry/alert.json +31 -0
- package/registry/article-layout.json +38 -0
- package/registry/avatar.json +31 -0
- package/registry/badge.json +33 -0
- package/registry/breadcrumbs.json +32 -0
- package/registry/button.json +34 -0
- package/registry/calendar.json +29 -0
- package/registry/card.json +30 -0
- package/registry/checkbox.json +33 -0
- package/registry/code-block.json +29 -0
- package/registry/collapsible.json +28 -0
- package/registry/combobox.json +30 -0
- package/registry/command.json +34 -0
- package/registry/contact-form.json +37 -0
- package/registry/cookie-consent.json +28 -0
- package/registry/copy-button.json +33 -0
- package/registry/cta-banner.json +32 -0
- package/registry/dashboard-shell.json +32 -0
- package/registry/date-picker.json +32 -0
- package/registry/description-list.json +28 -0
- package/registry/dialog.json +33 -0
- package/registry/dropdown-menu.json +33 -0
- package/registry/dropzone.json +31 -0
- package/registry/empty-state.json +32 -0
- package/registry/faq.json +31 -0
- package/registry/feature-grid.json +33 -0
- package/registry/field.json +29 -0
- package/registry/hero.json +32 -0
- package/registry/hover-card.json +28 -0
- package/registry/index.json +1633 -0
- package/registry/input-group.json +30 -0
- package/registry/input.json +29 -0
- package/registry/kbd.json +29 -0
- package/registry/label.json +29 -0
- package/registry/layout.json +32 -0
- package/registry/newsletter.json +35 -0
- package/registry/not-found.json +34 -0
- package/registry/page-header.json +29 -0
- package/registry/pagination.json +25 -0
- package/registry/popover.json +28 -0
- package/registry/pricing.json +35 -0
- package/registry/progress.json +28 -0
- package/registry/radio-group.json +28 -0
- package/registry/rating.json +32 -0
- package/registry/reading-progress.json +29 -0
- package/registry/relative-time.json +29 -0
- package/registry/scroll-area.json +28 -0
- package/registry/section-heading.json +30 -0
- package/registry/select.json +32 -0
- package/registry/separator.json +32 -0
- package/registry/settings-section.json +32 -0
- package/registry/share-buttons.json +34 -0
- package/registry/sheet.json +34 -0
- package/registry/sign-in.json +31 -0
- package/registry/site-footer.json +31 -0
- package/registry/site-header.json +37 -0
- package/registry/skeleton.json +28 -0
- package/registry/skip-link.json +30 -0
- package/registry/slider.json +28 -0
- package/registry/spinner.json +32 -0
- package/registry/stat.json +32 -0
- package/registry/steps.json +31 -0
- package/registry/switch.json +32 -0
- package/registry/table-of-contents.json +32 -0
- package/registry/table.json +28 -0
- package/registry/tabs.json +29 -0
- package/registry/textarea.json +29 -0
- package/registry/theme-toggle.json +34 -0
- package/registry/theme.json +16 -0
- package/registry/toast.json +29 -0
- package/registry/toggle-group.json +29 -0
- package/registry/tooltip.json +29 -0
- package/registry/users-table.json +36 -0
- package/registry/utils.json +19 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "section-heading",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Section Heading",
|
|
6
|
+
"description": "The eyebrow, title, description, and action that open a page section.",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://abackui.com/r/utils.json"
|
|
9
|
+
],
|
|
10
|
+
"categories": [
|
|
11
|
+
"Application"
|
|
12
|
+
],
|
|
13
|
+
"docs": "https://abackui.com/docs/components/section-heading",
|
|
14
|
+
"meta": {
|
|
15
|
+
"status": "beta",
|
|
16
|
+
"usedIn": [
|
|
17
|
+
"aback-tools",
|
|
18
|
+
"aback-launch",
|
|
19
|
+
"aback-group"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
{
|
|
24
|
+
"path": "components/ui/section-heading.tsx",
|
|
25
|
+
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst titleSizes = {\n sm: \"text-xl font-semibold\",\n default: \"text-2xl font-bold sm:text-3xl\",\n lg: \"text-3xl font-bold sm:text-4xl\",\n} as const;\n\nconst descriptionSizes = {\n sm: \"text-sm\",\n default: \"text-base\",\n lg: \"text-lg\",\n} as const;\n\nexport interface SectionHeadingProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n title: React.ReactNode;\n /** A short label above the title. */\n eyebrow?: React.ReactNode;\n description?: React.ReactNode;\n /** A link or button that belongs to the section, such as \"View all\". */\n action?: React.ReactNode;\n /** Heading level. A section under the page title is an h2. */\n as?: \"h2\" | \"h3\" | \"h4\";\n size?: keyof typeof titleSizes;\n align?: \"start\" | \"center\";\n /** Id for the heading, so the section can use `aria-labelledby`. */\n headingId?: string;\n}\n\n/**\n * The heading of a page section: eyebrow, title, one-line description, and an\n * optional action that moves to the right on wide screens. PageHeader is for\n * the page's h1; this is for everything below it.\n */\nfunction SectionHeading({\n title,\n eyebrow,\n description,\n action,\n as: Heading = \"h2\",\n size = \"default\",\n align = \"start\",\n headingId,\n className,\n ...props\n}: SectionHeadingProps) {\n const centered = align === \"center\";\n return (\n <div\n data-slot=\"section-heading\"\n className={cn(\n \"flex flex-col gap-4\",\n centered ? \"items-center text-center\" : \"md:flex-row md:items-end md:justify-between\",\n className,\n )}\n {...props}\n >\n <div className={cn(\"flex max-w-2xl flex-col gap-2\", centered && \"items-center\")}>\n {eyebrow ? (\n <p className=\"text-xs font-semibold uppercase tracking-wide text-link\">{eyebrow}</p>\n ) : null}\n <Heading\n id={headingId}\n className={cn(\"leading-tight tracking-tight text-foreground text-balance\", titleSizes[size])}\n >\n {title}\n </Heading>\n {description ? (\n <p className={cn(\"leading-relaxed text-muted-foreground text-pretty\", descriptionSizes[size])}>\n {description}\n </p>\n ) : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n );\n}\n\nexport { SectionHeading };\n",
|
|
26
|
+
"type": "registry:ui",
|
|
27
|
+
"target": "components/ui/section-heading.tsx"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "select",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Select",
|
|
6
|
+
"description": "A native select with a themed chevron and 36px height.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json"
|
|
12
|
+
],
|
|
13
|
+
"categories": [
|
|
14
|
+
"Forms"
|
|
15
|
+
],
|
|
16
|
+
"docs": "https://abackui.com/docs/components/select",
|
|
17
|
+
"meta": {
|
|
18
|
+
"status": "stable",
|
|
19
|
+
"usedIn": [
|
|
20
|
+
"aback-tools",
|
|
21
|
+
"aback-launch"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
{
|
|
26
|
+
"path": "components/ui/select.tsx",
|
|
27
|
+
"content": "import * as React from \"react\";\nimport { IconChevronDown } from \"@tabler/icons-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst selectClassName = [\n \"h-9 w-full appearance-none border border-input bg-background px-3 pr-9 text-sm text-foreground shadow-xs\",\n \"outline-none transition-[color,background-color,border-color,box-shadow] duration-200\",\n \"focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20\",\n].join(\" \");\n\nconst Select = React.forwardRef<HTMLSelectElement, React.ComponentProps<\"select\">>(\n function Select({ className, children, ...props }, ref) {\n return (\n <div data-slot=\"select\" className=\"relative w-full\">\n <select\n ref={ref}\n data-slot=\"select-control\"\n className={cn(selectClassName, className)}\n {...props}\n >\n {children}\n </select>\n <IconChevronDown\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\"\n />\n </div>\n );\n },\n);\nSelect.displayName = \"Select\";\n\nexport { Select, selectClassName };\n",
|
|
28
|
+
"type": "registry:ui",
|
|
29
|
+
"target": "components/ui/select.tsx"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "separator",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Separator",
|
|
6
|
+
"description": "A one-pixel rule for page chrome and grouped content.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"radix-ui"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json"
|
|
12
|
+
],
|
|
13
|
+
"categories": [
|
|
14
|
+
"Foundation"
|
|
15
|
+
],
|
|
16
|
+
"docs": "https://abackui.com/docs/components/separator",
|
|
17
|
+
"meta": {
|
|
18
|
+
"status": "stable",
|
|
19
|
+
"usedIn": [
|
|
20
|
+
"aback-launch",
|
|
21
|
+
"aback-group"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
{
|
|
26
|
+
"path": "components/ui/separator.tsx",
|
|
27
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Separator as SeparatorPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Separator({\n className,\n orientation = \"horizontal\",\n decorative = true,\n ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n return (\n <SeparatorPrimitive.Root\n data-slot=\"separator\"\n decorative={decorative}\n orientation={orientation}\n className={cn(\n \"shrink-0 bg-separator\",\n \"data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full\",\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Separator };\n",
|
|
28
|
+
"type": "registry:ui",
|
|
29
|
+
"target": "components/ui/separator.tsx"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "settings-section",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Settings section",
|
|
6
|
+
"description": "A workspace settings section with identity fields, preferences, a save bar, and a confirmed danger zone.",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://abackui.com/r/utils.json",
|
|
9
|
+
"https://abackui.com/r/alert-dialog.json",
|
|
10
|
+
"https://abackui.com/r/button.json",
|
|
11
|
+
"https://abackui.com/r/card.json",
|
|
12
|
+
"https://abackui.com/r/field.json",
|
|
13
|
+
"https://abackui.com/r/input.json",
|
|
14
|
+
"https://abackui.com/r/label.json",
|
|
15
|
+
"https://abackui.com/r/switch.json"
|
|
16
|
+
],
|
|
17
|
+
"categories": [
|
|
18
|
+
"Settings"
|
|
19
|
+
],
|
|
20
|
+
"docs": "https://abackui.com/docs/blocks/settings-section",
|
|
21
|
+
"meta": {
|
|
22
|
+
"status": "beta"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
{
|
|
26
|
+
"path": "components/blocks/settings-section.tsx",
|
|
27
|
+
"content": "\"use client\";\n\nimport { useState } from \"react\";\n\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Field } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Switch } from \"@/components/ui/switch\";\n\nexport interface SettingsValues {\n name: string;\n slug: string;\n weeklyDigest: boolean;\n deployAlerts: boolean;\n}\n\nexport interface SettingsSectionBlockProps {\n initialValues?: Partial<SettingsValues>;\n onSave?: (values: SettingsValues) => void;\n onDelete?: () => void;\n}\n\n/**\n * A complete workspace settings section: identity, notification preferences,\n * a save bar, and a destructive zone that requires an explicit confirmation.\n * It is a section, not a page; put it inside your settings layout.\n */\nexport function SettingsSectionBlock({\n initialValues,\n onSave,\n onDelete,\n}: SettingsSectionBlockProps) {\n const [values, setValues] = useState<SettingsValues>({\n name: initialValues?.name ?? \"Aback Group\",\n slug: initialValues?.slug ?? \"aback-group\",\n weeklyDigest: initialValues?.weeklyDigest ?? true,\n deployAlerts: initialValues?.deployAlerts ?? true,\n });\n const [saved, setSaved] = useState(false);\n\n function update<K extends keyof SettingsValues>(key: K, value: SettingsValues[K]) {\n setValues((current) => ({ ...current, [key]: value }));\n setSaved(false);\n }\n\n function handleSave() {\n onSave?.(values);\n setSaved(true);\n }\n\n return (\n <section aria-labelledby=\"workspace-settings-title\" className=\"flex flex-col gap-6\">\n <div>\n <h2\n id=\"workspace-settings-title\"\n className=\"text-xl font-semibold tracking-tight text-foreground\"\n >\n Workspace settings\n </h2>\n <p className=\"mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground\">\n These settings apply to every member of the workspace.\n </p>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle as=\"h3\" className=\"text-base\">\n General\n </CardTitle>\n <CardDescription>How the workspace appears to your team and in URLs.</CardDescription>\n </CardHeader>\n\n <CardContent className=\"flex flex-col gap-5\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field htmlFor=\"workspace-name\" label=\"Workspace name\" required>\n <Input\n id=\"workspace-name\"\n value={values.name}\n onChange={(event) => update(\"name\", event.target.value)}\n />\n </Field>\n <Field\n htmlFor=\"workspace-slug\"\n label=\"URL slug\"\n description=\"Lowercase letters, numbers, and dashes.\"\n >\n <Input\n id=\"workspace-slug\"\n value={values.slug}\n onChange={(event) => update(\"slug\", event.target.value)}\n />\n </Field>\n </div>\n\n <div className=\"flex flex-col divide-y divide-border border-t border-border\">\n <div className=\"flex items-start justify-between gap-4 py-3\">\n <div>\n <Label htmlFor=\"workspace-digest\">Weekly digest</Label>\n <p className=\"mt-0.5 text-xs leading-relaxed text-muted-foreground\">\n A Monday summary of deployments, invoices, and open reviews.\n </p>\n </div>\n <Switch\n id=\"workspace-digest\"\n checked={values.weeklyDigest}\n onCheckedChange={(checked) => update(\"weeklyDigest\", checked)}\n />\n </div>\n <div className=\"flex items-start justify-between gap-4 py-3\">\n <div>\n <Label htmlFor=\"workspace-alerts\">Deploy alerts</Label>\n <p className=\"mt-0.5 text-xs leading-relaxed text-muted-foreground\">\n Notify the workspace when a production deploy finishes or fails.\n </p>\n </div>\n <Switch\n id=\"workspace-alerts\"\n checked={values.deployAlerts}\n onCheckedChange={(checked) => update(\"deployAlerts\", checked)}\n />\n </div>\n </div>\n </CardContent>\n\n <CardFooter className=\"justify-between gap-3 border-t border-border pt-4\">\n <p role=\"status\" className=\"text-xs text-muted-foreground\">\n {saved ? \"Settings saved.\" : \"Changes are local until you save.\"}\n </p>\n <div className=\"flex gap-2\">\n <Button\n variant=\"ghost\"\n onClick={() => {\n setValues({\n name: initialValues?.name ?? \"Aback Group\",\n slug: initialValues?.slug ?? \"aback-group\",\n weeklyDigest: initialValues?.weeklyDigest ?? true,\n deployAlerts: initialValues?.deployAlerts ?? true,\n });\n setSaved(false);\n }}\n >\n Reset\n </Button>\n <Button onClick={handleSave}>Save changes</Button>\n </div>\n </CardFooter>\n </Card>\n\n <Card className=\"border-danger/40 dark:border-danger/40\">\n <CardHeader>\n <CardTitle as=\"h3\" className=\"text-base text-danger\">\n Danger zone\n </CardTitle>\n <CardDescription>\n Deleting the workspace removes its members, projects, and deploy history. This cannot\n be undone.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"outline\" className=\"border-danger/50 text-danger hover:bg-danger/5\">\n Delete workspace\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete {values.name}?</AlertDialogTitle>\n <AlertDialogDescription>\n Every member will lose access immediately, and the deploy history will be\n removed. This action cannot be undone.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep workspace</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={onDelete}\n >\n Delete workspace\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </CardContent>\n </Card>\n </section>\n );\n}\n",
|
|
28
|
+
"type": "registry:block",
|
|
29
|
+
"target": "components/blocks/settings-section.tsx"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "share-buttons",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Share Buttons",
|
|
6
|
+
"description": "Share links for X, LinkedIn, and more, with copy link and the native share sheet.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json",
|
|
12
|
+
"https://abackui.com/r/button.json",
|
|
13
|
+
"https://abackui.com/r/copy-button.json"
|
|
14
|
+
],
|
|
15
|
+
"categories": [
|
|
16
|
+
"Application"
|
|
17
|
+
],
|
|
18
|
+
"docs": "https://abackui.com/docs/components/share-buttons",
|
|
19
|
+
"meta": {
|
|
20
|
+
"status": "beta",
|
|
21
|
+
"usedIn": [
|
|
22
|
+
"aback-tools",
|
|
23
|
+
"aback-launch"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
{
|
|
28
|
+
"path": "components/ui/share-buttons.tsx",
|
|
29
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n IconBrandFacebook,\n IconBrandLinkedin,\n IconBrandReddit,\n IconBrandWhatsapp,\n IconBrandX,\n IconMail,\n IconShare,\n type Icon,\n} from \"@tabler/icons-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport { CopyButton } from \"@/components/ui/copy-button\";\n\nexport type ShareNetwork = \"x\" | \"linkedin\" | \"facebook\" | \"whatsapp\" | \"reddit\" | \"email\";\n\ninterface NetworkConfig {\n name: string;\n icon: Icon;\n href: (url: string, title: string) => string;\n}\n\nconst networks: Record<ShareNetwork, NetworkConfig> = {\n x: {\n name: \"X\",\n icon: IconBrandX,\n href: (url, title) =>\n `https://x.com/intent/post?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`,\n },\n linkedin: {\n name: \"LinkedIn\",\n icon: IconBrandLinkedin,\n href: (url) => `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`,\n },\n facebook: {\n name: \"Facebook\",\n icon: IconBrandFacebook,\n href: (url) => `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`,\n },\n whatsapp: {\n name: \"WhatsApp\",\n icon: IconBrandWhatsapp,\n href: (url, title) => `https://wa.me/?text=${encodeURIComponent(title ? `${title} ${url}` : url)}`,\n },\n reddit: {\n name: \"Reddit\",\n icon: IconBrandReddit,\n href: (url, title) =>\n `https://www.reddit.com/submit?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`,\n },\n email: {\n name: \"email\",\n icon: IconMail,\n href: (url, title) =>\n `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`,\n },\n};\n\n/** Builds the share URL for one network. Exported for custom layouts. */\nfunction shareUrl(network: ShareNetwork, url: string, title = \"\") {\n return networks[network].href(url, title);\n}\n\nfunction subscribeNothing() {\n return () => {};\n}\n\nexport interface ShareButtonsProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n /** The absolute URL being shared. */\n url: string;\n /** Used as the post text or email subject where the network supports it. */\n title?: string;\n networks?: ShareNetwork[];\n /** Add a copy-link button. */\n copyLink?: boolean;\n /** Add a native share button where the browser offers the Web Share API. */\n nativeShare?: boolean;\n /** Show network names next to the icons. */\n showLabels?: boolean;\n}\n\n/**\n * Share links for a page. Each network is a plain link that opens in a new\n * tab with `noopener`, so nothing loads from a third party until someone\n * chooses to share. Every control has an accessible name.\n */\nfunction ShareButtons({\n url,\n title = \"\",\n networks: selected = [\"x\", \"linkedin\"],\n copyLink = true,\n nativeShare = false,\n showLabels = false,\n className,\n \"aria-label\": ariaLabel = \"Share\",\n ...props\n}: ShareButtonsProps) {\n const canShare = React.useSyncExternalStore(\n subscribeNothing,\n () => typeof navigator !== \"undefined\" && typeof navigator.share === \"function\",\n () => false,\n );\n const size = showLabels ? \"sm\" : \"icon-sm\";\n\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n data-slot=\"share-buttons\"\n className={cn(\"flex flex-wrap items-center gap-1.5\", className)}\n {...props}\n >\n {nativeShare && canShare ? (\n <button\n type=\"button\"\n className={buttonVariants({ variant: \"outline\", size })}\n aria-label={showLabels ? undefined : \"Share\"}\n onClick={() => {\n navigator.share({ url, title: title || undefined }).catch(() => {\n // The visitor closed the share sheet; nothing to report.\n });\n }}\n >\n <IconShare aria-hidden=\"true\" />\n {showLabels ? \"Share\" : null}\n </button>\n ) : null}\n {selected.map((network) => {\n const config = networks[network];\n const Icon = config.icon;\n const label = network === \"email\" ? \"Share by email\" : `Share on ${config.name}`;\n return (\n <a\n key={network}\n href={shareUrl(network, url, title)}\n target={network === \"email\" ? undefined : \"_blank\"}\n rel={network === \"email\" ? undefined : \"noopener noreferrer\"}\n aria-label={showLabels ? undefined : label}\n title={showLabels ? undefined : label}\n className={cn(buttonVariants({ variant: \"outline\", size }), \"text-muted-foreground hover:text-foreground\")}\n >\n <Icon aria-hidden=\"true\" />\n {showLabels ? <span>{network === \"email\" ? \"Email\" : config.name}</span> : null}\n </a>\n );\n })}\n {copyLink ? (\n <CopyButton\n value={url}\n variant=\"outline\"\n label=\"Copy link\"\n copiedLabel=\"Link copied\"\n showLabel={showLabels}\n className=\"text-muted-foreground hover:text-foreground\"\n />\n ) : null}\n </div>\n );\n}\n\nexport { ShareButtons, shareUrl };\n",
|
|
30
|
+
"type": "registry:ui",
|
|
31
|
+
"target": "components/ui/share-buttons.tsx"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "sheet",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Sheet",
|
|
6
|
+
"description": "A modal panel that slides in from an edge for settings and filters.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react",
|
|
9
|
+
"radix-ui"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"https://abackui.com/r/utils.json"
|
|
13
|
+
],
|
|
14
|
+
"categories": [
|
|
15
|
+
"Overlays"
|
|
16
|
+
],
|
|
17
|
+
"docs": "https://abackui.com/docs/components/sheet",
|
|
18
|
+
"meta": {
|
|
19
|
+
"status": "beta",
|
|
20
|
+
"usedIn": [
|
|
21
|
+
"aback-tools",
|
|
22
|
+
"aback-launch",
|
|
23
|
+
"aback-group"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
{
|
|
28
|
+
"path": "components/ui/sheet.tsx",
|
|
29
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as SheetPrimitive } from \"radix-ui\";\nimport { IconX } from \"@tabler/icons-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Sheet = SheetPrimitive.Root;\nconst SheetTrigger = SheetPrimitive.Trigger;\nconst SheetClose = SheetPrimitive.Close;\nconst SheetPortal = SheetPrimitive.Portal;\n\ntype SheetSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst sideClasses: Record<SheetSide, string> = {\n right:\n \"inset-y-0 right-0 h-full w-3/4 max-w-sm border-l data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right\",\n left:\n \"inset-y-0 left-0 h-full w-3/4 max-w-sm border-r data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left\",\n top:\n \"inset-x-0 top-0 h-auto max-h-[85dvh] w-full border-b data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top\",\n bottom:\n \"inset-x-0 bottom-0 h-auto max-h-[85dvh] w-full border-t data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom\",\n};\n\nfunction SheetOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm dark:bg-black/70\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SheetContent({\n className,\n children,\n side = \"right\",\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n side?: SheetSide;\n showCloseButton?: boolean;\n}) {\n return (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content\n data-slot=\"sheet-content\"\n data-side={side}\n className={cn(\n \"fixed z-50 flex flex-col gap-4 overflow-y-auto border-border bg-popover p-6 text-popover-foreground shadow-2xl\",\n \"duration-300 data-[state=open]:animate-in data-[state=open]:duration-500 data-[state=closed]:animate-out\",\n sideClasses[side],\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton ? (\n <SheetPrimitive.Close\n data-slot=\"sheet-close\"\n className=\"absolute right-3 top-3 inline-flex size-8 items-center justify-center text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-popover focus-visible:outline-none\"\n >\n <IconX aria-hidden=\"true\" className=\"size-4\" />\n <span className=\"sr-only\">Close</span>\n </SheetPrimitive.Close>\n ) : null}\n </SheetPrimitive.Content>\n </SheetPortal>\n );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div data-slot=\"sheet-header\" className={cn(\"flex flex-col gap-1.5 pr-6\", className)} {...props} />;\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"sheet-footer\"\n className={cn(\"mt-auto flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n {...props}\n />\n );\n}\n\nfunction SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {\n return (\n <SheetPrimitive.Title\n data-slot=\"sheet-title\"\n className={cn(\"text-lg font-semibold tracking-tight\", className)}\n {...props}\n />\n );\n}\n\nfunction SheetDescription({\n className,\n ...props\n}: React.ComponentProps<typeof SheetPrimitive.Description>) {\n return (\n <SheetPrimitive.Description\n data-slot=\"sheet-description\"\n className={cn(\"text-sm leading-relaxed text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetPortal,\n SheetOverlay,\n SheetContent,\n SheetHeader,\n SheetFooter,\n SheetTitle,\n SheetDescription,\n};\n",
|
|
30
|
+
"type": "registry:ui",
|
|
31
|
+
"target": "components/ui/sheet.tsx"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "sign-in",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Sign in",
|
|
6
|
+
"description": "A complete sign-in card with email, password, remember me, and account links.",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://abackui.com/r/utils.json",
|
|
9
|
+
"https://abackui.com/r/button.json",
|
|
10
|
+
"https://abackui.com/r/card.json",
|
|
11
|
+
"https://abackui.com/r/checkbox.json",
|
|
12
|
+
"https://abackui.com/r/field.json",
|
|
13
|
+
"https://abackui.com/r/input.json",
|
|
14
|
+
"https://abackui.com/r/label.json"
|
|
15
|
+
],
|
|
16
|
+
"categories": [
|
|
17
|
+
"Authentication"
|
|
18
|
+
],
|
|
19
|
+
"docs": "https://abackui.com/docs/blocks/sign-in",
|
|
20
|
+
"meta": {
|
|
21
|
+
"status": "beta"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
{
|
|
25
|
+
"path": "components/blocks/sign-in.tsx",
|
|
26
|
+
"content": "\"use client\";\n\nimport { useState, type FormEvent } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Field } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nexport interface SignInValues {\n email: string;\n password: string;\n remember: boolean;\n}\n\nexport interface SignInBlockProps {\n onSubmit?: (values: SignInValues) => void;\n signUpHref?: string;\n forgotHref?: string;\n title?: string;\n description?: string;\n}\n\n/**\n * A complete sign-in card. It owns no authentication: pass `onSubmit` to call\n * your auth client, and swap the anchors for your router's link component.\n */\nexport function SignInBlock({\n onSubmit,\n signUpHref = \"#\",\n forgotHref = \"#\",\n title = \"Sign in\",\n description = \"Use the email you signed up with to continue to your workspace.\",\n}: SignInBlockProps) {\n const [email, setEmail] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [remember, setRemember] = useState(true);\n\n function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n onSubmit?.({ email, password, remember });\n }\n\n return (\n <section aria-labelledby=\"sign-in-title\" className=\"mx-auto w-full max-w-sm\">\n <div\n aria-hidden=\"true\"\n className=\"mb-6 flex size-9 items-center justify-center bg-primary text-sm font-bold text-primary-foreground\"\n >\n A\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle as=\"h2\" id=\"sign-in-title\" className=\"text-xl\">\n {title}\n </CardTitle>\n <CardDescription>{description}</CardDescription>\n </CardHeader>\n\n <CardContent>\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n <Field htmlFor=\"sign-in-email\" label=\"Email\" required>\n <Input\n id=\"sign-in-email\"\n name=\"email\"\n type=\"email\"\n autoComplete=\"email\"\n inputMode=\"email\"\n placeholder=\"you@company.com\"\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n required\n />\n </Field>\n\n <Field htmlFor=\"sign-in-password\" label=\"Password\" required>\n <Input\n id=\"sign-in-password\"\n name=\"password\"\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n onChange={(event) => setPassword(event.target.value)}\n required\n />\n </Field>\n\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"sign-in-remember\"\n name=\"remember\"\n checked={remember}\n onCheckedChange={(checked) => setRemember(checked === true)}\n />\n <Label htmlFor=\"sign-in-remember\" className=\"font-normal\">\n Remember me\n </Label>\n </div>\n <a\n href={forgotHref}\n className=\"text-xs font-medium text-link underline-offset-4 hover:underline\"\n >\n Forgot password?\n </a>\n </div>\n\n <Button type=\"submit\" className=\"w-full\">\n Sign in\n </Button>\n </form>\n </CardContent>\n\n <CardFooter className=\"justify-center border-t border-border pt-4 text-sm text-muted-foreground\">\n New to Aback?{\" \"}\n <a\n href={signUpHref}\n className=\"ml-1 font-medium text-link underline-offset-4 hover:underline\"\n >\n Create an account\n </a>\n </CardFooter>\n </Card>\n </section>\n );\n}\n",
|
|
27
|
+
"type": "registry:block",
|
|
28
|
+
"target": "components/blocks/sign-in.tsx"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "site-footer",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Site footer",
|
|
6
|
+
"description": "Brand column, link columns, and a legal bar that collapse cleanly from four columns to one.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [],
|
|
11
|
+
"categories": [
|
|
12
|
+
"Marketing"
|
|
13
|
+
],
|
|
14
|
+
"docs": "https://abackui.com/docs/blocks/site-footer",
|
|
15
|
+
"meta": {
|
|
16
|
+
"status": "beta",
|
|
17
|
+
"usedIn": [
|
|
18
|
+
"aback-tools",
|
|
19
|
+
"aback-launch",
|
|
20
|
+
"aback-group"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
{
|
|
25
|
+
"path": "components/blocks/site-footer.tsx",
|
|
26
|
+
"content": "import type { ReactNode } from \"react\";\nimport { IconArrowUpRight } from \"@tabler/icons-react\";\n\nexport interface SiteFooterLink {\n title: string;\n href: string;\n /** Opens in a new tab with noopener and marks the link as external. */\n external?: boolean;\n}\n\nexport interface SiteFooterColumn {\n title: string;\n links: SiteFooterLink[];\n}\n\nexport interface SiteFooterBlockProps {\n brand: ReactNode;\n /** One or two sentences about the product. */\n tagline?: ReactNode;\n columns: SiteFooterColumn[];\n /** The copyright line, such as \"© 2026 Acme Inc.\" */\n copyright: ReactNode;\n /** Privacy, terms, and similar links in the bottom bar. */\n legal?: SiteFooterLink[];\n}\n\nfunction FooterLink({ link, className }: { link: SiteFooterLink; className: string }) {\n return (\n <a\n href={link.href}\n target={link.external ? \"_blank\" : undefined}\n rel={link.external ? \"noopener noreferrer\" : undefined}\n className={className}\n >\n {link.title}\n {link.external ? (\n <>\n <IconArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n <span className=\"sr-only\">(opens in a new tab)</span>\n </>\n ) : null}\n </a>\n );\n}\n\nconst linkClass =\n \"inline-flex items-center gap-1 text-xs text-muted-foreground outline-none transition-colors duration-200 hover:text-foreground focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\";\n\n/**\n * A site footer with a brand column, link columns, and a legal bar. Columns\n * collapse to two on tablets and one on phones. It renders the page's\n * contentinfo landmark, so use it once per page.\n */\nexport function SiteFooterBlock({ brand, tagline, columns, copyright, legal = [] }: SiteFooterBlockProps) {\n return (\n <footer className=\"border-t border-separator bg-background\">\n <div className=\"mx-auto grid w-full max-w-6xl gap-x-8 gap-y-10 px-4 py-10 sm:grid-cols-2 md:px-6 lg:grid-cols-4\">\n <div className=\"flex flex-col gap-3\">\n {brand}\n {tagline ? (\n <p className=\"max-w-[34ch] text-xs leading-relaxed text-muted-foreground\">{tagline}</p>\n ) : null}\n </div>\n {columns.map((column) => (\n <nav key={column.title} aria-label={column.title}>\n <h2 className=\"mb-3 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {column.title}\n </h2>\n <ul className=\"flex flex-col gap-2\">\n {column.links.map((link) => (\n <li key={link.href}>\n <FooterLink link={link} className={linkClass} />\n </li>\n ))}\n </ul>\n </nav>\n ))}\n </div>\n <div className=\"bg-muted/40\">\n <div className=\"mx-auto flex w-full max-w-6xl flex-col items-center gap-2 px-4 py-3 text-center sm:flex-row sm:justify-between sm:text-left md:px-6\">\n <p className=\"text-xs text-muted-foreground\">{copyright}</p>\n {legal.length ? (\n <ul className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1\">\n {legal.map((link) => (\n <li key={link.href}>\n <FooterLink link={link} className={linkClass} />\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n </div>\n </footer>\n );\n}\n",
|
|
27
|
+
"type": "registry:block",
|
|
28
|
+
"target": "components/blocks/site-footer.tsx"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "site-header",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "Site header",
|
|
6
|
+
"description": "A sticky header with brand, primary navigation, optional search, theme toggle, actions, and a mobile sheet.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json",
|
|
12
|
+
"https://abackui.com/r/button.json",
|
|
13
|
+
"https://abackui.com/r/kbd.json",
|
|
14
|
+
"https://abackui.com/r/sheet.json",
|
|
15
|
+
"https://abackui.com/r/theme-toggle.json"
|
|
16
|
+
],
|
|
17
|
+
"categories": [
|
|
18
|
+
"Marketing"
|
|
19
|
+
],
|
|
20
|
+
"docs": "https://abackui.com/docs/blocks/site-header",
|
|
21
|
+
"meta": {
|
|
22
|
+
"status": "beta",
|
|
23
|
+
"usedIn": [
|
|
24
|
+
"aback-tools",
|
|
25
|
+
"aback-launch",
|
|
26
|
+
"aback-group"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
{
|
|
31
|
+
"path": "components/blocks/site-header.tsx",
|
|
32
|
+
"content": "\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport { IconMenu2, IconSearch } from \"@tabler/icons-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { ThemeToggle } from \"@/components/ui/theme-toggle\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface SiteHeaderLink {\n title: string;\n href: string;\n}\n\nexport interface SiteHeaderBlockProps {\n /** Your logo and name, usually a link home. */\n brand: ReactNode;\n links: SiteHeaderLink[];\n /** The href of the current section. A link is active when the path starts with it. */\n activePath?: string;\n /** Primary actions on the right, such as a sign-in link or a CTA. */\n actions?: ReactNode;\n /** Renders a search trigger. Omit it and no search control appears. */\n onSearch?: () => void;\n /** Shortcut shown on the search trigger. Only show one you actually bind. */\n searchShortcut?: string;\n showThemeToggle?: boolean;\n}\n\nfunction isActive(activePath: string | undefined, href: string) {\n if (!activePath) return false;\n if (href === \"/\") return activePath === \"/\";\n return activePath === href || activePath.startsWith(`${href.replace(/\\/$/, \"\")}/`);\n}\n\nfunction linkClass(active: boolean) {\n return cn(\n \"inline-flex h-9 items-center px-3 text-sm font-medium outline-none transition-colors duration-200\",\n \"focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n active\n ? \"bg-brand-wash text-link\"\n : \"text-muted-foreground hover:bg-muted hover:text-foreground\",\n );\n}\n\n/**\n * A sticky site header: brand, primary navigation, optional search, theme\n * toggle, and actions. Below md the links move into a sheet. Links are plain\n * anchors; swap them for your router's link component.\n */\nexport function SiteHeaderBlock({\n brand,\n links,\n activePath,\n actions,\n onSearch,\n searchShortcut,\n showThemeToggle = true,\n}: SiteHeaderBlockProps) {\n const [open, setOpen] = useState(false);\n\n return (\n <header className=\"sticky top-0 z-40 w-full border-b border-separator bg-background/95 backdrop-blur-sm\">\n <div className=\"mx-auto flex h-16 w-full max-w-6xl items-center gap-4 px-4 md:px-6\">\n <div className=\"shrink-0\">{brand}</div>\n\n <nav aria-label=\"Primary\" className=\"ml-2 hidden items-center gap-1 md:flex\">\n {links.map((link) => {\n const active = isActive(activePath, link.href);\n return (\n <a\n key={link.href}\n href={link.href}\n aria-current={active ? \"page\" : undefined}\n className={linkClass(active)}\n >\n {link.title}\n </a>\n );\n })}\n </nav>\n\n <div className=\"ml-auto flex items-center gap-1\">\n {onSearch ? (\n <>\n <button\n type=\"button\"\n onClick={onSearch}\n className=\"hidden h-9 w-52 items-center gap-2 border border-border bg-muted/30 px-3 text-sm text-muted-foreground outline-none transition-colors duration-200 hover:border-primary-300 hover:bg-background hover:text-foreground focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background lg:flex\"\n >\n <IconSearch aria-hidden=\"true\" className=\"size-4\" />\n <span className=\"flex-1 text-left\">Search</span>\n {searchShortcut ? <Kbd>{searchShortcut}</Kbd> : null}\n </button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Search\"\n onClick={onSearch}\n className=\"lg:hidden\"\n >\n <IconSearch aria-hidden=\"true\" />\n </Button>\n </>\n ) : null}\n\n {showThemeToggle ? <ThemeToggle /> : null}\n\n {actions ? (\n <>\n <span aria-hidden=\"true\" className=\"mx-2 hidden h-5 w-px bg-border md:block\" />\n <div className=\"hidden items-center gap-2 md:flex\">{actions}</div>\n </>\n ) : null}\n\n <Sheet open={open} onOpenChange={setOpen}>\n <SheetTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" aria-label=\"Open menu\" className=\"md:hidden\">\n <IconMenu2 aria-hidden=\"true\" />\n </Button>\n </SheetTrigger>\n <SheetContent side=\"right\" className=\"w-72 gap-0 p-0\">\n <SheetHeader className=\"h-16 justify-center border-b border-separator px-4\">\n <SheetTitle>Menu</SheetTitle>\n <SheetDescription className=\"sr-only\">Site navigation</SheetDescription>\n </SheetHeader>\n <nav aria-label=\"Mobile\" className=\"flex flex-col gap-1 p-3\">\n {links.map((link) => {\n const active = isActive(activePath, link.href);\n return (\n <a\n key={link.href}\n href={link.href}\n onClick={() => setOpen(false)}\n aria-current={active ? \"page\" : undefined}\n className={cn(linkClass(active), \"h-10 w-full\")}\n >\n {link.title}\n </a>\n );\n })}\n </nav>\n {actions ? (\n <div className=\"mt-auto flex flex-col gap-2 border-t border-separator p-4 [&>*]:w-full\">\n {actions}\n </div>\n ) : null}\n </SheetContent>\n </Sheet>\n </div>\n </div>\n </header>\n );\n}\n",
|
|
33
|
+
"type": "registry:block",
|
|
34
|
+
"target": "components/blocks/site-header.tsx"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "skeleton",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Skeleton",
|
|
6
|
+
"description": "A loading placeholder that preserves layout.",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://abackui.com/r/utils.json"
|
|
9
|
+
],
|
|
10
|
+
"categories": [
|
|
11
|
+
"Feedback"
|
|
12
|
+
],
|
|
13
|
+
"docs": "https://abackui.com/docs/components/skeleton",
|
|
14
|
+
"meta": {
|
|
15
|
+
"status": "stable",
|
|
16
|
+
"usedIn": [
|
|
17
|
+
"aback-launch"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"path": "components/ui/skeleton.tsx",
|
|
23
|
+
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"skeleton\"\n aria-hidden=\"true\"\n className={cn(\"animate-pulse bg-muted\", className)}\n {...props}\n />\n );\n}\n\nexport { Skeleton };\n",
|
|
24
|
+
"type": "registry:ui",
|
|
25
|
+
"target": "components/ui/skeleton.tsx"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "skip-link",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Skip Link",
|
|
6
|
+
"description": "The first focusable element, which jumps keyboard users past the header to the content.",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://abackui.com/r/utils.json"
|
|
9
|
+
],
|
|
10
|
+
"categories": [
|
|
11
|
+
"Navigation"
|
|
12
|
+
],
|
|
13
|
+
"docs": "https://abackui.com/docs/components/skip-link",
|
|
14
|
+
"meta": {
|
|
15
|
+
"status": "beta",
|
|
16
|
+
"usedIn": [
|
|
17
|
+
"aback-tools",
|
|
18
|
+
"aback-launch",
|
|
19
|
+
"aback-group"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
{
|
|
24
|
+
"path": "components/ui/skip-link.tsx",
|
|
25
|
+
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface SkipLinkProps extends React.ComponentProps<\"a\"> {\n /** The id of the main content, with its hash. */\n href?: string;\n}\n\n/**\n * The first focusable element on a page. It stays hidden until it receives\n * keyboard focus, then lets a keyboard or switch user jump past the header.\n * The target needs a matching id, and `tabIndex={-1}` if it is not focusable.\n */\nfunction SkipLink({\n href = \"#main-content\",\n children = \"Skip to content\",\n className,\n ...props\n}: SkipLinkProps) {\n return (\n <a\n data-slot=\"skip-link\"\n href={href}\n className={cn(\n \"sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[100]\",\n \"focus:border focus:border-border focus:bg-background focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-foreground focus:shadow-lg\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className,\n )}\n {...props}\n >\n {children}\n </a>\n );\n}\n\nexport { SkipLink };\n",
|
|
26
|
+
"type": "registry:ui",
|
|
27
|
+
"target": "components/ui/skip-link.tsx"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "slider",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Slider",
|
|
6
|
+
"description": "A single-value range control with keyboard support and a square thumb.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"radix-ui"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json"
|
|
12
|
+
],
|
|
13
|
+
"categories": [
|
|
14
|
+
"Forms"
|
|
15
|
+
],
|
|
16
|
+
"docs": "https://abackui.com/docs/components/slider",
|
|
17
|
+
"meta": {
|
|
18
|
+
"status": "beta"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"path": "components/ui/slider.tsx",
|
|
23
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slider as SliderPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface SliderProps extends React.ComponentProps<typeof SliderPrimitive.Root> {\n /** Accessible name for the thumb. Required unless aria-labelledby is used. */\n label?: string;\n}\n\nfunction Slider({\n className,\n label,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n}: SliderProps) {\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n className={cn(\n \"relative flex w-full touch-none select-none items-center\",\n \"data-[orientation=vertical]:h-44 data-[orientation=vertical]:w-4 data-[orientation=vertical]:flex-col\",\n \"data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className={cn(\n \"relative grow overflow-hidden bg-secondary\",\n \"data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full\",\n \"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\",\n )}\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n aria-label={label}\n aria-labelledby={ariaLabelledBy}\n className={cn(\n \"block size-4 shrink-0 border-2 border-background bg-primary shadow-sm\",\n \"outline-none transition-colors\",\n \"focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none\",\n )}\n />\n </SliderPrimitive.Root>\n );\n}\n\nexport { Slider };\n",
|
|
24
|
+
"type": "registry:ui",
|
|
25
|
+
"target": "components/ui/slider.tsx"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "spinner",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Spinner",
|
|
6
|
+
"description": "A loading indicator with a screen-reader label.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json"
|
|
12
|
+
],
|
|
13
|
+
"categories": [
|
|
14
|
+
"Feedback"
|
|
15
|
+
],
|
|
16
|
+
"docs": "https://abackui.com/docs/components/spinner",
|
|
17
|
+
"meta": {
|
|
18
|
+
"status": "stable",
|
|
19
|
+
"usedIn": [
|
|
20
|
+
"aback-tools",
|
|
21
|
+
"aback-launch"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
{
|
|
26
|
+
"path": "components/ui/spinner.tsx",
|
|
27
|
+
"content": "import * as React from \"react\";\nimport { IconLoader2 } from \"@tabler/icons-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst spinnerSizes = {\n sm: \"size-4\",\n md: \"size-5\",\n lg: \"size-6\",\n} as const;\n\nexport interface SpinnerProps extends React.ComponentProps<\"span\"> {\n /** Announced to assistive technology. */\n label?: string;\n size?: keyof typeof spinnerSizes;\n}\n\nfunction Spinner({\n label = \"Loading\",\n size = \"md\",\n className,\n ...props\n}: SpinnerProps) {\n return (\n <span\n data-slot=\"spinner\"\n role=\"status\"\n className={cn(\"inline-flex items-center justify-center text-muted-foreground\", className)}\n {...props}\n >\n <IconLoader2 aria-hidden=\"true\" className={cn(\"animate-spin\", spinnerSizes[size])} />\n <span className=\"sr-only\">{label}</span>\n </span>\n );\n}\n\nexport { Spinner };\n",
|
|
28
|
+
"type": "registry:ui",
|
|
29
|
+
"target": "components/ui/spinner.tsx"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "stat",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Stat",
|
|
6
|
+
"description": "A labelled number with unit, trend, hint, and tone, plus a responsive grid.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json"
|
|
12
|
+
],
|
|
13
|
+
"categories": [
|
|
14
|
+
"Data Display"
|
|
15
|
+
],
|
|
16
|
+
"docs": "https://abackui.com/docs/components/stat",
|
|
17
|
+
"meta": {
|
|
18
|
+
"status": "beta",
|
|
19
|
+
"usedIn": [
|
|
20
|
+
"aback-tools",
|
|
21
|
+
"aback-launch"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
{
|
|
26
|
+
"path": "components/ui/stat.tsx",
|
|
27
|
+
"content": "import * as React from \"react\";\nimport { IconArrowDownRight, IconArrowRight, IconArrowUpRight } from \"@tabler/icons-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type StatTone = \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nconst toneText: Record<StatTone, string> = {\n neutral: \"text-foreground\",\n success: \"text-success\",\n warning: \"text-warning\",\n danger: \"text-danger\",\n info: \"text-info\",\n};\n\nconst toneBorder: Record<StatTone, string> = {\n neutral: \"border-border\",\n success: \"border-success/30\",\n warning: \"border-warning/30\",\n danger: \"border-danger/30\",\n info: \"border-info/30\",\n};\n\nconst toneSurface: Record<StatTone, string> = {\n neutral: \"border-border bg-muted/40\",\n success: \"border-success/30 bg-success-soft\",\n warning: \"border-warning/30 bg-warning-soft\",\n danger: \"border-danger/30 bg-danger-soft\",\n info: \"border-info/30 bg-info-soft\",\n};\n\nconst valueSize = {\n sm: \"text-sm\",\n default: \"text-xl leading-tight\",\n lg: \"text-3xl leading-none tracking-tight\",\n} as const;\n\nexport interface StatTrend {\n direction: \"up\" | \"down\" | \"flat\";\n /** Words that carry the change on their own, such as \"+12% vs last week\". */\n label: string;\n /** Defaults to success for up, danger for down, neutral for flat. */\n tone?: StatTone;\n}\n\nconst defaultTrendTone: Record<StatTrend[\"direction\"], StatTone> = {\n up: \"success\",\n down: \"danger\",\n flat: \"neutral\",\n};\n\nconst trendIcon = {\n up: IconArrowUpRight,\n down: IconArrowDownRight,\n flat: IconArrowRight,\n} as const;\n\nexport interface StatProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n label: React.ReactNode;\n /** The formatted value: \"$1,234\", \"42%\", \"3 h 20 min\". */\n value: React.ReactNode;\n /** A unit after the value, in muted text. */\n unit?: React.ReactNode;\n /** A sub-line under the value. */\n hint?: React.ReactNode;\n /** A 16px icon, shown in a brand chip. Decorative. */\n icon?: React.ReactNode;\n /** Colours the value. Pair it with words in the label, hint, or a badge. */\n tone?: StatTone;\n /** Tints the whole tile with the tone, for a recommended or alert tile. */\n emphasis?: boolean;\n /** A badge after the label. */\n badge?: React.ReactNode;\n trend?: StatTrend;\n size?: keyof typeof valueSize;\n}\n\n/**\n * One labelled number. The label and value are a real description list, so\n * the label is announced with the value. Change the value freely; it is not a\n * live region, so a ticking number does not interrupt a screen reader.\n */\nfunction Stat({\n label,\n value,\n unit,\n hint,\n icon,\n tone = \"neutral\",\n emphasis = false,\n badge,\n trend,\n size = \"default\",\n className,\n ...props\n}: StatProps) {\n const TrendIcon = trend ? trendIcon[trend.direction] : null;\n const trendTone = trend ? (trend.tone ?? defaultTrendTone[trend.direction]) : \"neutral\";\n\n return (\n <div\n data-slot=\"stat\"\n data-tone={tone}\n className={cn(\n \"flex min-w-0 items-start gap-3 border p-4\",\n emphasis ? toneSurface[tone] : cn(\"bg-card\", toneBorder[tone]),\n className,\n )}\n {...props}\n >\n {icon ? (\n <span\n aria-hidden=\"true\"\n className=\"flex size-9 shrink-0 items-center justify-center bg-brand-chip text-brand-icon [&_svg:not([class*='size-'])]:size-4\"\n >\n {icon}\n </span>\n ) : null}\n <dl className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <dt className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs font-medium text-muted-foreground\">\n {label}\n {badge}\n </dt>\n <dd\n data-slot=\"stat-value\"\n className={cn(\"font-semibold tabular-nums break-words\", valueSize[size], toneText[tone])}\n >\n {value}\n {unit ? (\n <span className=\"ml-1 text-xs font-normal tracking-normal text-muted-foreground\">\n {unit}\n </span>\n ) : null}\n </dd>\n {trend && TrendIcon ? (\n <dd\n data-slot=\"stat-trend\"\n data-direction={trend.direction}\n className={cn(\"flex items-center gap-1 text-xs font-medium\", toneText[trendTone])}\n >\n <TrendIcon aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n {trend.label}\n </dd>\n ) : null}\n {hint ? <dd className=\"text-xs text-muted-foreground\">{hint}</dd> : null}\n </dl>\n </div>\n );\n}\n\nconst gridColumns = {\n 2: \"grid-cols-1 sm:grid-cols-2\",\n 3: \"grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\",\n 4: \"grid-cols-1 sm:grid-cols-2 xl:grid-cols-4\",\n} as const;\n\nexport interface StatGridProps extends React.ComponentProps<\"div\"> {\n /** Columns at the widest breakpoint. Always one column on phones. */\n columns?: keyof typeof gridColumns;\n /** Draws the stats as one joined panel with hairline dividers. */\n joined?: boolean;\n}\n\n/** A responsive grid of Stat tiles. */\nfunction StatGrid({ columns = 3, joined = false, className, ...props }: StatGridProps) {\n return (\n <div\n data-slot=\"stat-grid\"\n className={cn(\n \"grid\",\n gridColumns[columns],\n joined\n ? \"gap-px border border-border bg-border [&>[data-slot=stat]]:border-0\"\n : \"gap-3\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Stat, StatGrid };\n",
|
|
28
|
+
"type": "registry:ui",
|
|
29
|
+
"target": "components/ui/stat.tsx"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://abackui.com/schema/registry-item.json",
|
|
3
|
+
"name": "steps",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"title": "Steps",
|
|
6
|
+
"description": "An ordered sequence with complete, current, and upcoming states for onboarding and how-it-works sections.",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"@tabler/icons-react"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://abackui.com/r/utils.json"
|
|
12
|
+
],
|
|
13
|
+
"categories": [
|
|
14
|
+
"Data Display"
|
|
15
|
+
],
|
|
16
|
+
"docs": "https://abackui.com/docs/components/steps",
|
|
17
|
+
"meta": {
|
|
18
|
+
"status": "beta",
|
|
19
|
+
"usedIn": [
|
|
20
|
+
"aback-launch"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
{
|
|
25
|
+
"path": "components/ui/steps.tsx",
|
|
26
|
+
"content": "import * as React from \"react\";\nimport { IconCheck } from \"@tabler/icons-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type StepStatus = \"complete\" | \"current\" | \"upcoming\";\n\nconst statusText: Record<StepStatus, string> = {\n complete: \"Completed\",\n current: \"Current step\",\n upcoming: \"Not started\",\n};\n\nexport interface StepsProps extends React.ComponentProps<\"ol\"> {\n orientation?: \"vertical\" | \"horizontal\";\n}\n\n/**\n * An ordered sequence: onboarding, a checkout, a \"how it works\" section, or a\n * setup guide. It is an `<ol>`, so the count and position are announced, and\n * numbers come from a CSS counter rather than props.\n */\nfunction Steps({ orientation = \"vertical\", className, ...props }: StepsProps) {\n return (\n <ol\n data-slot=\"steps\"\n data-orientation={orientation}\n className={cn(\n \"group/steps [counter-reset:step]\",\n orientation === \"vertical\"\n ? \"flex flex-col\"\n : \"grid grid-cols-1 gap-6 sm:auto-cols-fr sm:grid-flow-col sm:gap-4\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface StepProps extends Omit<React.ComponentProps<\"li\">, \"title\"> {\n title: React.ReactNode;\n description?: React.ReactNode;\n status?: StepStatus;\n /** Replaces the sr-only status prefix, for localization. */\n statusLabel?: string;\n}\n\nfunction Step({\n title,\n description,\n status = \"upcoming\",\n statusLabel,\n className,\n children,\n ...props\n}: StepProps) {\n return (\n <li\n data-slot=\"step\"\n data-status={status}\n aria-current={status === \"current\" ? \"step\" : undefined}\n className={cn(\n \"relative flex gap-4 [counter-increment:step] last:[&>[data-slot=step-connector]]:hidden\",\n \"group-data-[orientation=vertical]/steps:pb-8 group-data-[orientation=vertical]/steps:last:pb-0\",\n \"sm:group-data-[orientation=horizontal]/steps:flex-col sm:group-data-[orientation=horizontal]/steps:gap-3\",\n className,\n )}\n {...props}\n >\n <span\n aria-hidden=\"true\"\n data-slot=\"step-connector\"\n className={cn(\n \"absolute bg-border\",\n \"group-data-[orientation=vertical]/steps:left-4 group-data-[orientation=vertical]/steps:top-9 group-data-[orientation=vertical]/steps:bottom-1 group-data-[orientation=vertical]/steps:w-px\",\n \"group-data-[orientation=horizontal]/steps:hidden sm:group-data-[orientation=horizontal]/steps:block\",\n \"sm:group-data-[orientation=horizontal]/steps:left-11 sm:group-data-[orientation=horizontal]/steps:right-1 sm:group-data-[orientation=horizontal]/steps:top-4 sm:group-data-[orientation=horizontal]/steps:h-px\",\n status === \"complete\" && \"bg-primary-200 dark:bg-primary-800\",\n )}\n />\n <span\n aria-hidden=\"true\"\n data-slot=\"step-marker\"\n className={cn(\n \"relative z-10 flex size-8 shrink-0 items-center justify-center text-xs font-bold tabular-nums\",\n status === \"upcoming\"\n ? \"border border-border bg-background text-muted-foreground\"\n : \"bg-brand-fill text-on-brand-fill\",\n status === \"current\" &&\n \"ring-2 ring-primary-200 ring-offset-2 ring-offset-background dark:ring-primary-800\",\n )}\n >\n {status === \"complete\" ? (\n <IconCheck className=\"size-4\" />\n ) : (\n <span className=\"before:content-[counter(step)]\" />\n )}\n </span>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1 pt-1\">\n <p\n className={cn(\n \"text-sm font-semibold\",\n status === \"upcoming\" ? \"text-muted-foreground\" : \"text-foreground\",\n )}\n >\n <span className=\"sr-only\">{statusLabel ?? statusText[status]}: </span>\n {title}\n </p>\n {description ? (\n <p className=\"text-sm leading-relaxed text-muted-foreground\">{description}</p>\n ) : null}\n {children}\n </div>\n </li>\n );\n}\n\nexport { Step, Steps };\n",
|
|
27
|
+
"type": "registry:ui",
|
|
28
|
+
"target": "components/ui/steps.tsx"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|