@nikala-ui/core 0.10.1 → 0.11.0-nightly.b01fa01

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.
Files changed (106) hide show
  1. package/package.json +1 -1
  2. package/registry/banner.json +1 -1
  3. package/registry/bubble.json +18 -0
  4. package/registry/button-group.json +20 -0
  5. package/registry/button.json +1 -1
  6. package/registry/checkbox.json +4 -1
  7. package/registry/combobox.json +1 -1
  8. package/registry/command.json +3 -2
  9. package/registry/context-menu.json +1 -1
  10. package/registry/create-app-updater.json +13 -0
  11. package/registry/create-chat-scroll.json +13 -0
  12. package/registry/create-document-tabs.json +13 -0
  13. package/registry/create-drop-zone.json +13 -0
  14. package/registry/create-global-shortcut.json +13 -0
  15. package/registry/create-pagination.json +13 -0
  16. package/registry/create-tauri-window.json +13 -0
  17. package/registry/dialog.json +1 -1
  18. package/registry/dropdown-menu.json +3 -2
  19. package/registry/dropzone.json +21 -0
  20. package/registry/field.json +1 -1
  21. package/registry/footer.json +18 -0
  22. package/registry/forgot-password-01.json +28 -0
  23. package/registry/form-message.json +3 -2
  24. package/registry/hero-01.json +22 -0
  25. package/registry/icon-button.json +1 -1
  26. package/registry/index.json +432 -5
  27. package/registry/login-01.json +28 -0
  28. package/registry/marker.json +17 -0
  29. package/registry/marquee.json +17 -0
  30. package/registry/message.json +17 -0
  31. package/registry/navbar.json +19 -0
  32. package/registry/navigation-menu.json +22 -0
  33. package/registry/number-input.json +1 -1
  34. package/registry/otp-verification-01.json +26 -0
  35. package/registry/pagination.json +22 -0
  36. package/registry/popover.json +4 -1
  37. package/registry/rating.json +19 -0
  38. package/registry/register-01.json +31 -0
  39. package/registry/resizable.json +1 -1
  40. package/registry/review-card.json +23 -0
  41. package/registry/scroll-area.json +1 -1
  42. package/registry/select.json +3 -2
  43. package/registry/sheet.json +1 -1
  44. package/registry/sidebar.json +24 -0
  45. package/registry/spinner.json +1 -1
  46. package/registry/stat.json +19 -0
  47. package/registry/status.json +1 -1
  48. package/registry/switch.json +4 -1
  49. package/registry/table.json +17 -0
  50. package/registry/tabs.json +4 -1
  51. package/registry/theme-manager.json +4 -3
  52. package/registry/timeline.json +18 -0
  53. package/registry/titlebar-tabs.json +24 -0
  54. package/registry/titlebar.json +26 -0
  55. package/registry/toggle-group.json +21 -0
  56. package/registry/updater-modal.json +26 -0
  57. package/src/registry/blocks/forgot-password-01.tsx +141 -0
  58. package/src/registry/blocks/hero-01.tsx +28 -0
  59. package/src/registry/blocks/login-01.tsx +231 -0
  60. package/src/registry/blocks/otp-verification-01.tsx +170 -0
  61. package/src/registry/blocks/register-01.tsx +318 -0
  62. package/src/registry/components/ui/banner.tsx +0 -2
  63. package/src/registry/components/ui/bubble.tsx +142 -0
  64. package/src/registry/components/ui/button-group.tsx +42 -0
  65. package/src/registry/components/ui/button.tsx +1 -1
  66. package/src/registry/components/ui/checkbox.tsx +1 -1
  67. package/src/registry/components/ui/combobox.tsx +83 -18
  68. package/src/registry/components/ui/command.tsx +101 -105
  69. package/src/registry/components/ui/context-menu.tsx +67 -22
  70. package/src/registry/components/ui/dialog.tsx +42 -32
  71. package/src/registry/components/ui/dropdown-menu.tsx +69 -31
  72. package/src/registry/components/ui/dropzone.tsx +189 -0
  73. package/src/registry/components/ui/field.tsx +7 -3
  74. package/src/registry/components/ui/footer.tsx +247 -0
  75. package/src/registry/components/ui/form-message.tsx +2 -2
  76. package/src/registry/components/ui/icon-button.tsx +1 -1
  77. package/src/registry/components/ui/marker.tsx +102 -0
  78. package/src/registry/components/ui/marquee.tsx +118 -0
  79. package/src/registry/components/ui/message.tsx +168 -0
  80. package/src/registry/components/ui/navbar.tsx +375 -0
  81. package/src/registry/components/ui/navigation-menu.tsx +358 -0
  82. package/src/registry/components/ui/number-input.tsx +3 -3
  83. package/src/registry/components/ui/pagination.tsx +258 -0
  84. package/src/registry/components/ui/popover.tsx +1 -1
  85. package/src/registry/components/ui/rating.tsx +185 -0
  86. package/src/registry/components/ui/resizable.tsx +3 -1
  87. package/src/registry/components/ui/review-card.tsx +195 -0
  88. package/src/registry/components/ui/scroll-area.tsx +13 -3
  89. package/src/registry/components/ui/select.tsx +16 -19
  90. package/src/registry/components/ui/sheet.tsx +58 -53
  91. package/src/registry/components/ui/sidebar.tsx +692 -0
  92. package/src/registry/components/ui/spinner.tsx +1 -1
  93. package/src/registry/components/ui/stat.tsx +245 -0
  94. package/src/registry/components/ui/status.tsx +7 -5
  95. package/src/registry/components/ui/switch.tsx +1 -1
  96. package/src/registry/components/ui/table.tsx +160 -0
  97. package/src/registry/components/ui/tabs.tsx +1 -1
  98. package/src/registry/components/ui/theme-toggle.tsx +80 -79
  99. package/src/registry/components/ui/timeline.tsx +350 -0
  100. package/src/registry/components/ui/titlebar-tabs.tsx +412 -0
  101. package/src/registry/components/ui/titlebar.tsx +399 -0
  102. package/src/registry/components/ui/toggle-group.tsx +203 -0
  103. package/src/registry/components/ui/updater-modal.tsx +254 -0
  104. package/src/registry/index.ts +3 -3
  105. package/src/registry/metadata.ts +184 -5
  106. package/src/registry/providers/theme-transitions.ts +81 -50
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "toggle-group",
3
+ "title": "Toggle Group",
4
+ "description": "A set of two-state buttons that can be toggled on or off with single or multiple selection modes.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority"
10
+ ],
11
+ "registryDependencies": [
12
+ "create-controllable-signal"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "ui/toggle-group.tsx",
17
+ "content": "import {\n createContext,\n useContext,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { createControllableSignal } from \"@/hooks/create-controllable-signal\";\nimport { cn } from \"@/lib/cn\";\n\nexport const toggleGroupItemVariants = cva(\n \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n outline:\n \"border border-border bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground\",\n },\n size: {\n default: \"h-9 px-3 min-w-9\",\n sm: \"h-8 px-2 text-xs min-w-8\",\n lg: \"h-10 px-3 min-w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\ninterface ToggleGroupContextValue {\n type: \"single\" | \"multiple\";\n value: Accessor<any>;\n onItemSelect: (itemValue: string) => void;\n variant: Accessor<\"default\" | \"outline\">;\n size: Accessor<\"default\" | \"sm\" | \"lg\">;\n disabled: Accessor<boolean>;\n}\n\nconst ToggleGroupContext = createContext<ToggleGroupContextValue>();\n\nexport interface ToggleGroupProps<T extends \"single\" | \"multiple\" = \"single\">\n extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onChange\">,\n VariantProps<typeof toggleGroupItemVariants> {\n /** Mode of selection: single choice or multiple choices */\n type?: T;\n /** Controlled value: string for 'single', string[] for 'multiple' */\n value?: T extends \"multiple\" ? string[] : string;\n /** Uncontrolled default value */\n defaultValue?: T extends \"multiple\" ? string[] : string;\n /** Callback fired when selection changes */\n onChange?: (value: T extends \"multiple\" ? string[] : string) => void;\n /** Layout orientation */\n orientation?: \"horizontal\" | \"vertical\";\n /** Disables all buttons in the group */\n disabled?: boolean;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Root container for grouping connected toggle items with shared single/multiple selection state.\n */\nexport const ToggleGroup = <T extends \"single\" | \"multiple\" = \"single\">(\n props: ToggleGroupProps<T>\n) => {\n const [local, rest] = splitProps(props, [\n \"type\",\n \"value\",\n \"defaultValue\",\n \"onChange\",\n \"orientation\",\n \"variant\",\n \"size\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n const type = () => local.type ?? (\"single\" as T);\n const variant = () => local.variant ?? \"default\";\n const size = () => local.size ?? \"default\";\n const disabled = () => local.disabled ?? false;\n const orientation = () => local.orientation ?? \"horizontal\";\n\n const [currentValue, setCurrentValue] = createControllableSignal<any>({\n value: () => local.value,\n defaultValue: local.defaultValue ?? (type() === \"multiple\" ? [] : undefined),\n onChange: (val) => local.onChange?.(val),\n });\n\n const onItemSelect = (itemValue: string) => {\n if (disabled()) return;\n\n if (type() === \"multiple\") {\n const currentList = Array.isArray(currentValue()) ? currentValue() : [];\n if (currentList.includes(itemValue)) {\n setCurrentValue(currentList.filter((v: string) => v !== itemValue));\n } else {\n setCurrentValue([...currentList, itemValue]);\n }\n } else {\n const current = currentValue();\n if (current === itemValue) {\n setCurrentValue(undefined);\n } else {\n setCurrentValue(itemValue);\n }\n }\n };\n\n const contextValue: ToggleGroupContextValue = {\n type: type(),\n value: currentValue,\n onItemSelect,\n variant,\n size,\n disabled,\n };\n\n return (\n <ToggleGroupContext.Provider value={contextValue}>\n <div\n role=\"group\"\n data-orientation={orientation()}\n class={cn(\n \"flex items-center justify-center gap-1 rounded-lg\",\n orientation() === \"vertical\" ? \"flex-col\" : \"flex-row\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </ToggleGroupContext.Provider>\n );\n};\n\nexport interface ToggleGroupItemProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof toggleGroupItemVariants> {\n /** Unique value representing this toggle item within the group */\n value: string;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Individual toggle button item within a ToggleGroup.\n */\nexport const ToggleGroupItem: Component<ToggleGroupItemProps> = (props) => {\n const context = useContext(ToggleGroupContext);\n\n if (!context) {\n throw new Error(\"ToggleGroupItem must be used within a ToggleGroup\");\n }\n\n const [local, rest] = splitProps(props, [\n \"value\",\n \"variant\",\n \"size\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n const isSelected = () => {\n const groupValue = context.value();\n if (context.type === \"multiple\") {\n return Array.isArray(groupValue) && groupValue.includes(local.value);\n }\n return groupValue === local.value;\n };\n\n const isDisabled = () => local.disabled || context.disabled();\n const itemVariant = () => local.variant || context.variant();\n const itemSize = () => local.size || context.size();\n\n return (\n <button\n type=\"button\"\n role={context.type === \"single\" ? \"radio\" : \"checkbox\"}\n aria-checked={isSelected()}\n data-state={isSelected() ? \"on\" : \"off\"}\n disabled={isDisabled()}\n onClick={() => context.onItemSelect(local.value)}\n class={cn(\n toggleGroupItemVariants({\n variant: itemVariant(),\n size: itemSize(),\n }),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </button>\n );\n};\n",
18
+ "type": "registry:ui"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "updater-modal",
3
+ "title": "Updater Modal",
4
+ "description": "An automated application auto-updater dialog for Tauri v2 with release notes preview, download progress bar, and relaunch actions.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "lucide-solid"
10
+ ],
11
+ "registryDependencies": [
12
+ "dialog",
13
+ "button",
14
+ "badge",
15
+ "progress",
16
+ "alert",
17
+ "create-app-updater"
18
+ ],
19
+ "files": [
20
+ {
21
+ "path": "ui/updater-modal.tsx",
22
+ "content": "import {\n Show,\n splitProps,\n type Component,\n type ParentComponent,\n type JSX,\n} from \"solid-js\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { Alert, AlertTitle, AlertDescription } from \"@/components/ui/alert\";\nimport type {\n CreateAppUpdaterReturn,\n UpdateManifestInfo,\n UpdateProgressInfo,\n} from \"@/hooks/create-app-updater\";\nimport {\n Download,\n RotateCcw,\n CheckCircle2,\n AlertTriangle,\n ArrowRight,\n Package,\n} from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\nfunction formatBytes(bytes: number): string {\n if (!bytes || bytes === 0) return \"0 MB\";\n const mb = bytes / (1024 * 1024);\n return `${mb.toFixed(1)} MB`;\n}\n\n/* --- 1. Sub-components for Clean Modularity & Zero Repetition --- */\n\nexport interface UpdaterHeaderProps {\n appName: string;\n status: string;\n info?: UpdateManifestInfo | null;\n}\n\nexport const UpdaterHeader: Component<UpdaterHeaderProps> = (props) => (\n <DialogHeader class=\"space-y-1.5 text-left\">\n <div class=\"flex items-center justify-between\">\n <div class=\"size-8 rounded-lg bg-primary/10 text-primary flex items-center justify-center\">\n <Package class=\"size-4\" />\n </div>\n <Show when={props.info?.version}>\n <div class=\"flex items-center gap-1.5 text-xs font-mono\">\n <span class=\"text-muted-foreground\">{props.info?.currentVersion || \"v1.0.0\"}</span>\n <ArrowRight class=\"size-3 text-muted-foreground\" />\n <Badge variant=\"default\" class=\"text-xs px-1.5 py-0 bg-primary text-primary-foreground font-semibold\">\n {props.info?.version}\n </Badge>\n </div>\n </Show>\n </div>\n\n <DialogTitle class=\"text-base font-bold tracking-tight text-foreground\">\n {props.status === \"downloaded\"\n ? \"Update Ready to Install\"\n : props.status === \"downloading\"\n ? \"Downloading Update...\"\n : props.status === \"up-to-date\"\n ? \"Up to Date\"\n : `New Update Available for ${props.appName}`}\n </DialogTitle>\n\n <DialogDescription class=\"text-xs text-muted-foreground leading-relaxed\">\n {props.status === \"downloaded\"\n ? \"The update has been downloaded and verified. Restart to apply changes.\"\n : props.status === \"downloading\"\n ? \"Please wait while update packages are being downloaded.\"\n : props.status === \"up-to-date\"\n ? `${props.appName} is running on the latest version.`\n : \"A new version with features and bug fixes is ready to install.\"}\n </DialogDescription>\n </DialogHeader>\n);\n\nexport interface UpdaterReleaseNotesProps {\n body?: string;\n date?: string;\n}\n\nexport const UpdaterReleaseNotes: Component<UpdaterReleaseNotesProps> = (props) => (\n <div class=\"space-y-1\">\n <div class=\"flex items-center justify-between text-xs text-muted-foreground font-medium\">\n <span>Release Notes</span>\n <Show when={props.date}>\n <span class=\"text-[11px] font-mono\">{props.date}</span>\n </Show>\n </div>\n <pre class=\"max-h-28 overflow-y-auto rounded-md border border-border bg-black/5 dark:bg-black/50 p-2.5 text-xs text-foreground font-mono whitespace-pre-wrap leading-relaxed select-text shadow-inner\">\n {props.body}\n </pre>\n </div>\n);\n\nexport interface UpdaterProgressBarProps {\n progress: UpdateProgressInfo;\n}\n\nexport const UpdaterProgressBar: Component<UpdaterProgressBarProps> = (props) => (\n <div class=\"space-y-1.5 py-1\">\n <div class=\"flex items-center justify-between text-xs font-mono\">\n <span class=\"text-muted-foreground font-sans\">Progress</span>\n <span class=\"font-semibold text-primary\">{props.progress.percentage}%</span>\n </div>\n <Progress value={props.progress.percentage} class=\"h-1.5\" />\n <div class=\"flex items-center justify-between text-[11px] font-mono text-muted-foreground\">\n <span>{formatBytes(props.progress.downloaded)}</span>\n <span>{formatBytes(props.progress.total)}</span>\n </div>\n </div>\n);\n\n/* --- 2. Main UpdaterModal Compound Component --- */\n\nexport interface UpdaterModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n updater: CreateAppUpdaterReturn;\n appName?: string;\n class?: string;\n}\n\nexport const UpdaterModal: Component<UpdaterModalProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"open\",\n \"onOpenChange\",\n \"updater\",\n \"appName\",\n \"class\",\n ]);\n\n const appName = () => local.appName || \"Nikala Desktop\";\n const status = () => local.updater.status();\n const info = () => local.updater.updateInfo();\n const progress = () => local.updater.progress();\n const error = () => local.updater.error();\n\n return (\n <Dialog open={local.open} onOpenChange={local.onOpenChange}>\n <DialogContent class={cn(\"sm:max-w-sm\", local.class)}>\n {/* Header */}\n <UpdaterHeader\n appName={appName()}\n status={status()}\n info={info()}\n />\n\n {/* Error Alert */}\n <Show when={error()}>\n <Alert variant=\"destructive\" class=\"py-2 text-xs\">\n <AlertTriangle class=\"size-3.5\" />\n <AlertTitle class=\"font-semibold text-xs\">Update Failed</AlertTitle>\n <AlertDescription class=\"text-[11px]\">{error()}</AlertDescription>\n </Alert>\n </Show>\n\n {/* Release Notes */}\n <Show when={info()?.body && (status() === \"available\" || status() === \"downloading\")}>\n <UpdaterReleaseNotes body={info()?.body} date={info()?.date} />\n </Show>\n\n {/* Progress Bar */}\n <Show when={status() === \"downloading\"}>\n <UpdaterProgressBar progress={progress()} />\n </Show>\n\n {/* Success Callout */}\n <Show when={status() === \"downloaded\"}>\n <div class=\"flex items-center gap-2.5 p-2.5 rounded-md border border-primary/30 bg-primary/5 text-xs text-foreground\">\n <CheckCircle2 class=\"size-4 text-primary shrink-0\" />\n <p class=\"font-medium text-foreground text-xs\">Package downloaded & verified.</p>\n </div>\n </Show>\n\n {/* Actions Footer */}\n <DialogFooter class=\"flex-row items-center justify-end gap-1.5 pt-1\">\n <Show when={status() === \"available\" || status() === \"error\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => local.onOpenChange(false)}\n class=\"text-xs h-7.5 px-3 cursor-pointer\"\n >\n Later\n </Button>\n <Button\n variant=\"default\"\n size=\"sm\"\n onClick={() => local.updater.downloadAndInstall()}\n class=\"text-xs h-7.5 px-3 gap-1.5 cursor-pointer font-medium\"\n >\n <Download class=\"size-3.5\" />\n <span>Update Now</span>\n </Button>\n </Show>\n\n <Show when={status() === \"downloading\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n class=\"text-xs h-7.5 w-full opacity-70 justify-center\"\n >\n Downloading ({progress().percentage}%)\n </Button>\n </Show>\n\n <Show when={status() === \"downloaded\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => local.onOpenChange(false)}\n class=\"text-xs h-7.5 px-3 cursor-pointer\"\n >\n Later\n </Button>\n <Button\n variant=\"default\"\n size=\"sm\"\n onClick={() => local.updater.relaunch()}\n class=\"text-xs h-7.5 px-3 gap-1.5 cursor-pointer font-semibold shadow-xs\"\n >\n <RotateCcw class=\"size-3.5\" />\n <span>Relaunch</span>\n </Button>\n </Show>\n\n <Show when={status() === \"up-to-date\" || status() === \"idle\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => local.onOpenChange(false)}\n class=\"text-xs h-7.5 px-3 cursor-pointer\"\n >\n Close\n </Button>\n </Show>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n};\n",
23
+ "type": "registry:ui"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,141 @@
1
+ import { createSignal, Show, type Component } from "solid-js";
2
+ import { createForm } from "@nikala-ui/hooks";
3
+ import {
4
+ Card,
5
+ CardHeader,
6
+ CardTitle,
7
+ CardDescription,
8
+ CardContent,
9
+ CardFooter,
10
+ } from "../components/ui/card";
11
+ import { Form } from "../components/ui/form";
12
+ import { Field, FieldLabel } from "../components/ui/field";
13
+ import { FormMessage } from "../components/ui/form-message";
14
+ import { Button } from "../components/ui/button";
15
+ import { Input } from "../components/ui/input";
16
+ import { Alert, AlertTitle, AlertDescription } from "../components/ui/alert";
17
+ import { Lock, Mail, ArrowLeft, ArrowRight, CheckCircle2, RotateCcw } from "lucide-solid";
18
+
19
+ export const ForgotPassword01: Component = () => {
20
+ const [sent, setSent] = createSignal(false);
21
+ const [submittedEmail, setSubmittedEmail] = createSignal("");
22
+
23
+ const form = createForm({
24
+ initialValues: {
25
+ email: "",
26
+ },
27
+ validate: (values) => {
28
+ const errors: Record<string, string> = {};
29
+ if (!values.email.trim()) {
30
+ errors.email = "Email is required";
31
+ } else if (!values.email.includes("@")) {
32
+ errors.email = "Please enter a valid email address";
33
+ }
34
+ return errors;
35
+ },
36
+ onSubmit: async (values) => {
37
+ await new Promise((resolve) => setTimeout(resolve, 1000));
38
+ setSubmittedEmail(values.email);
39
+ setSent(true);
40
+ },
41
+ });
42
+
43
+ const isFormValid = () =>
44
+ form.values().email.trim() !== "" && form.values().email.includes("@");
45
+
46
+ const handleReset = () => {
47
+ setSent(false);
48
+ form.resetForm();
49
+ };
50
+
51
+ return (
52
+ <div class="@container w-full min-h-[520px] flex items-center justify-center p-4 sm:p-6 md:p-10">
53
+ <Card class="w-full max-w-md border-border shadow-md bg-card">
54
+ {/* Card Header */}
55
+ <CardHeader class="space-y-2 text-center pb-4">
56
+ <div class="mx-auto size-12 rounded-lg bg-primary/10 text-primary flex items-center justify-center mb-1 shadow-2xs">
57
+ <Show when={sent()} fallback={<Lock class="size-6" />}>
58
+ <Mail class="size-6" />
59
+ </Show>
60
+ </div>
61
+ <CardTitle class="text-2xl font-bold tracking-tight">
62
+ {sent() ? "Check your email" : "Reset your password"}
63
+ </CardTitle>
64
+ <CardDescription class="text-xs sm:text-sm">
65
+ {sent()
66
+ ? `We have sent a secure password reset link to ${submittedEmail()}`
67
+ : "Enter the email associated with your account and we will send you a reset link"}
68
+ </CardDescription>
69
+ </CardHeader>
70
+
71
+ <CardContent class="space-y-4">
72
+ <Show
73
+ when={sent()}
74
+ fallback={
75
+ <Form onSubmit={form.handleSubmit} loading={form.isSubmitting()} class="space-y-4">
76
+ <Field>
77
+ <FieldLabel for="fp-email" class="text-xs sm:text-sm">Email address</FieldLabel>
78
+ <Input
79
+ id="fp-email"
80
+ type="email"
81
+ placeholder="name@company.com"
82
+ value={form.values().email}
83
+ onInput={form.handleChange("email")}
84
+ onBlur={form.handleBlur("email")}
85
+ autocomplete="email"
86
+ />
87
+ <FormMessage form={form} name="email" />
88
+ </Field>
89
+
90
+ <Button
91
+ type="submit"
92
+ class="w-full font-medium"
93
+ disabled={form.isSubmitting() || !isFormValid()}
94
+ >
95
+ <Show when={form.isSubmitting()} fallback={<>Send Reset Link <ArrowRight class="ml-2 size-4" /></>}>
96
+ Sending instructions...
97
+ </Show>
98
+ </Button>
99
+ </Form>
100
+ }
101
+ >
102
+ <div class="space-y-4">
103
+ <Alert class="bg-emerald-500/10 border-emerald-500/30 text-emerald-500 py-3">
104
+ <CheckCircle2 class="size-4 text-emerald-500 shrink-0" />
105
+ <AlertTitle class="text-xs font-semibold text-foreground">Instructions Sent</AlertTitle>
106
+ <AlertDescription class="text-[11px] text-muted-foreground">
107
+ If an account exists for <span class="font-medium text-foreground font-mono">{submittedEmail()}</span>, you will receive an email shortly.
108
+ </AlertDescription>
109
+ </Alert>
110
+
111
+ <div class="flex items-center justify-between text-xs text-muted-foreground pt-1">
112
+ <span>Didn't get the email?</span>
113
+ <Button
114
+ variant="link"
115
+ size="sm"
116
+ type="button"
117
+ onClick={handleReset}
118
+ class="h-auto p-0 text-xs text-primary font-semibold gap-1 cursor-pointer"
119
+ >
120
+ <RotateCcw class="size-3" /> Try another email
121
+ </Button>
122
+ </div>
123
+ </div>
124
+ </Show>
125
+ </CardContent>
126
+
127
+ {/* Card Footer */}
128
+ <CardFooter class="justify-center border-t border-border/50 py-4 text-xs text-muted-foreground">
129
+ <a
130
+ href="/blocks/login-01"
131
+ class="inline-flex items-center gap-1.5 text-foreground hover:text-primary transition-colors font-medium"
132
+ >
133
+ <ArrowLeft class="size-3.5" /> Back to sign in
134
+ </a>
135
+ </CardFooter>
136
+ </Card>
137
+ </div>
138
+ );
139
+ };
140
+
141
+ export default ForgotPassword01;
@@ -0,0 +1,28 @@
1
+ import { Component } from "solid-js";
2
+ import { Button } from "../components/ui/button";
3
+ import { Badge } from "../components/ui/badge";
4
+ import { ArrowRight } from "lucide-solid";
5
+
6
+ export default function Hero01() {
7
+ return (
8
+ <section class="w-full py-12 md:py-24 lg:py-32 flex flex-col items-center justify-center text-center">
9
+ <Badge variant="secondary" class="mb-4">
10
+ Nikala UI Blocks
11
+ </Badge>
12
+ <h1 class="text-4xl font-bold tracking-tight sm:text-6xl text-foreground max-w-3xl">
13
+ Build faster with copy-paste SolidJS blocks
14
+ </h1>
15
+ <p class="mt-6 text-lg text-muted-foreground max-w-2xl">
16
+ Pre-designed, fully responsive marketing and application blocks built natively for Tailwind CSS v4.
17
+ </p>
18
+ <div class="mt-8 flex items-center gap-4">
19
+ <Button size="lg">
20
+ Get Started <ArrowRight class="ml-2 size-4" />
21
+ </Button>
22
+ <Button variant="outline" size="lg">
23
+ Documentation
24
+ </Button>
25
+ </div>
26
+ </section>
27
+ );
28
+ }
@@ -0,0 +1,231 @@
1
+ import { createSignal, Show, type Component } from "solid-js";
2
+ import { createForm } from "@nikala-ui/hooks";
3
+ import { Form } from "../components/ui/form";
4
+ import { Field, FieldLabel } from "../components/ui/field";
5
+ import { FormMessage } from "../components/ui/form-message";
6
+ import { Button } from "../components/ui/button";
7
+ import { Input } from "../components/ui/input";
8
+ import { Checkbox } from "../components/ui/checkbox";
9
+ import { Label } from "../components/ui/label";
10
+ import { Separator } from "../components/ui/separator";
11
+ import { Eye, EyeOff, Sparkles, ArrowRight } from "lucide-solid";
12
+
13
+ export const Login01: Component = () => {
14
+ const [showPassword, setShowPassword] = createSignal(false);
15
+
16
+ const form = createForm({
17
+ initialValues: {
18
+ email: "",
19
+ password: "",
20
+ rememberMe: true,
21
+ },
22
+ validate: (values) => {
23
+ const errors: Record<string, string> = {};
24
+ if (!values.email) {
25
+ errors.email = "Email is required";
26
+ } else if (!values.email.includes("@")) {
27
+ errors.email = "Please enter a valid email address";
28
+ }
29
+ if (!values.password) {
30
+ errors.password = "Password is required";
31
+ }
32
+ return errors;
33
+ },
34
+ onSubmit: async (values) => {
35
+ // Simulate API request
36
+ await new Promise((resolve) => setTimeout(resolve, 1200));
37
+ },
38
+ });
39
+
40
+ const isFormValid = () =>
41
+ form.values().email.trim() !== "" &&
42
+ form.values().email.includes("@") &&
43
+ form.values().password.trim() !== "";
44
+
45
+ return (
46
+ <div class="@container w-full min-h-[560px] @5xl:min-h-[700px] grid grid-cols-1 @5xl:grid-cols-2 bg-background text-foreground rounded-lg border border-border overflow-hidden shadow-xs">
47
+ {/* Left Column: Login Form */}
48
+ <div class="flex flex-col justify-between p-6 @md:p-10 @5xl:p-12 w-full">
49
+ {/* Brand Header */}
50
+ <div class="flex items-center gap-2.5">
51
+ <div class="size-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm shadow-2xs shrink-0">
52
+ N
53
+ </div>
54
+ <span class="font-bold text-lg tracking-tight">Nikala UI</span>
55
+ </div>
56
+
57
+ {/* Form Container */}
58
+ <div class="mx-auto w-full max-w-sm my-auto py-6 @md:py-8">
59
+ <div class="space-y-1.5 text-left mb-6">
60
+ <h1 class="text-2xl @md:text-3xl font-bold tracking-tight text-foreground">
61
+ Welcome back
62
+ </h1>
63
+ <p class="text-xs @md:text-sm text-muted-foreground">
64
+ Enter your credentials to access your account dashboard
65
+ </p>
66
+ </div>
67
+
68
+ {/* Social OAuth Buttons */}
69
+ <div class="grid grid-cols-1 @xs:grid-cols-2 gap-2.5 mb-6">
70
+ <Button variant="outline" class="w-full justify-center text-xs @md:text-sm h-9">
71
+ <svg class="size-4 mr-2 shrink-0" viewBox="0 0 24 24" fill="currentColor">
72
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
73
+ </svg>
74
+ GitHub
75
+ </Button>
76
+ <Button variant="outline" class="w-full justify-center text-xs @md:text-sm h-9">
77
+ <svg class="size-4 mr-2 shrink-0" viewBox="0 0 24 24">
78
+ <path
79
+ fill="#4285F4"
80
+ d="M23.745 12.27c0-.7-.06-1.4-.19-2.07H12v4.51h6.6c-.29 1.52-1.14 2.82-2.4 3.68v3.05h3.88c2.27-2.09 3.66-5.17 3.66-9.17z"
81
+ />
82
+ <path
83
+ fill="#34A853"
84
+ d="M12 24c3.24 0 5.95-1.08 7.93-2.91l-3.88-3.05c-1.08.72-2.45 1.16-4.05 1.16-3.12 0-5.77-2.1-6.72-4.93H1.25v3.15C3.26 21.36 7.33 24 12 24z"
85
+ />
86
+ <path
87
+ fill="#FBBC05"
88
+ d="M5.28 14.27c-.25-.72-.38-1.49-.38-2.27s.13-1.55.38-2.27V6.58H1.25C.45 8.18 0 10.03 0 12s.45 3.82 1.25 5.42l4.03-3.15z"
89
+ />
90
+ <path
91
+ fill="#EA4335"
92
+ d="M12 4.75c1.77 0 3.35.61 4.6 1.8l3.42-3.42C17.95 1.19 15.24 0 12 0 7.33 0 3.26 2.64 1.25 6.58l4.03 3.15c.95-2.83 3.6-4.98 6.72-4.98z"
93
+ />
94
+ </svg>
95
+ Google
96
+ </Button>
97
+ </div>
98
+
99
+ <div class="relative mb-6">
100
+ <div class="absolute inset-0 flex items-center">
101
+ <Separator />
102
+ </div>
103
+ <div class="relative flex justify-center text-xs uppercase">
104
+ <span class="bg-background px-3 text-muted-foreground font-medium text-[11px]">
105
+ Or continue with email
106
+ </span>
107
+ </div>
108
+ </div>
109
+
110
+ {/* Form with Nikala UI Form & Field ecosystem */}
111
+ <Form onSubmit={form.handleSubmit} loading={form.isSubmitting()} class="space-y-4">
112
+ <Field>
113
+ <FieldLabel for="login-email" class="text-xs @md:text-sm">Email address</FieldLabel>
114
+ <Input
115
+ id="login-email"
116
+ type="email"
117
+ placeholder="name@company.com"
118
+ value={form.values().email}
119
+ onInput={form.handleChange("email")}
120
+ onBlur={form.handleBlur("email")}
121
+ autocomplete="email"
122
+ />
123
+ <FormMessage form={form} name="email" />
124
+ </Field>
125
+
126
+ <Field>
127
+ <div class="flex items-center justify-between">
128
+ <FieldLabel for="login-password" class="text-xs @md:text-sm">Password</FieldLabel>
129
+ <a
130
+ href="/blocks/forgot-password-01"
131
+ class="text-[11px] @md:text-xs text-primary font-medium hover:underline"
132
+ >
133
+ Forgot password?
134
+ </a>
135
+ </div>
136
+ <div class="relative">
137
+ <Input
138
+ id="login-password"
139
+ type={showPassword() ? "text" : "password"}
140
+ placeholder="••••••••"
141
+ value={form.values().password}
142
+ onInput={form.handleChange("password")}
143
+ onBlur={form.handleBlur("password")}
144
+ autocomplete="current-password"
145
+ class="pr-10"
146
+ />
147
+ <button
148
+ type="button"
149
+ onClick={() => setShowPassword(!showPassword())}
150
+ class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors p-1 cursor-pointer"
151
+ aria-label={showPassword() ? "Hide password" : "Show password"}
152
+ >
153
+ <Show when={showPassword()} fallback={<Eye class="size-4" />}>
154
+ <EyeOff class="size-4" />
155
+ </Show>
156
+ </button>
157
+ </div>
158
+ <FormMessage form={form} name="password" />
159
+ </Field>
160
+
161
+ <div class="flex items-center space-x-2 pt-1">
162
+ <Checkbox
163
+ id="remember"
164
+ checked={form.values().rememberMe}
165
+ onChange={(checked) => form.setFieldValue("rememberMe", checked)}
166
+ />
167
+ <Label
168
+ for="remember"
169
+ class="text-xs font-normal text-muted-foreground cursor-pointer select-none"
170
+ >
171
+ Remember me for 30 days
172
+ </Label>
173
+ </div>
174
+
175
+ <Button
176
+ type="submit"
177
+ class="w-full mt-2 font-medium"
178
+ disabled={form.isSubmitting() || !isFormValid()}
179
+ >
180
+ <Show when={form.isSubmitting()} fallback={<>Sign In <ArrowRight class="ml-2 size-4" /></>}>
181
+ Signing in...
182
+ </Show>
183
+ </Button>
184
+ </Form>
185
+
186
+ <p class="text-center text-xs text-muted-foreground mt-6">
187
+ Don't have an account?{" "}
188
+ <a href="/blocks/register-01" class="text-primary font-semibold hover:underline">
189
+ Sign up
190
+ </a>
191
+ </p>
192
+ </div>
193
+
194
+ {/* Footer info */}
195
+ <div class="text-center @md:text-left text-xs text-muted-foreground pt-4 @md:pt-0">
196
+ By signing in, you agree to our{" "}
197
+ <a href="#terms" class="underline hover:text-foreground">Terms of Service</a>{" "}
198
+ and{" "}
199
+ <a href="#privacy" class="underline hover:text-foreground">Privacy Policy</a>.
200
+ </div>
201
+ </div>
202
+
203
+ {/* Right Column: Hero Visual & Art / Testimonial Showcase */}
204
+ <div class="hidden @5xl:flex relative flex-col justify-between p-10 bg-muted/40 border-l border-border overflow-hidden">
205
+ <div class="absolute inset-0 bg-gradient-to-tr from-primary/10 via-transparent to-primary/5 pointer-events-none" />
206
+
207
+ <div class="relative z-10 flex items-center gap-2 text-sm font-semibold text-foreground/80">
208
+ <Sparkles class="size-4 text-primary" />
209
+ <span>Production Ready SolidJS UI</span>
210
+ </div>
211
+
212
+ <div class="relative z-10 max-w-md space-y-6">
213
+ <blockquote class="text-xl @md:text-2xl font-semibold tracking-tight text-foreground leading-snug">
214
+ "Nikala UI transformed the way our team builds web applications with SolidJS. Fine-grained reactivity meets Tailwind v4 simplicity."
215
+ </blockquote>
216
+ <div class="space-y-1">
217
+ <p class="text-sm font-semibold text-foreground">Davit Kakhidze</p>
218
+ <p class="text-xs text-muted-foreground">Head of Engineering at Studio</p>
219
+ </div>
220
+ </div>
221
+
222
+ <div class="relative z-10 flex items-center justify-between text-xs text-muted-foreground">
223
+ <span>Honoring Niko Pirosmani</span>
224
+ <span>© 2026 Nikala UI</span>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ );
229
+ };
230
+
231
+ export default Login01;
@@ -0,0 +1,170 @@
1
+ import { createSignal, onMount, onCleanup, Show, type Component } from "solid-js";
2
+ import {
3
+ Card,
4
+ CardHeader,
5
+ CardTitle,
6
+ CardDescription,
7
+ CardContent,
8
+ CardFooter,
9
+ } from "../components/ui/card";
10
+ import { Form } from "../components/ui/form";
11
+ import { Button } from "../components/ui/button";
12
+ import { PinInput, PinInputInput } from "../components/ui/pin-input";
13
+ import { Alert, AlertTitle, AlertDescription } from "../components/ui/alert";
14
+ import { Badge } from "../components/ui/badge";
15
+ import { KeyRound, Mail, CheckCircle2, RotateCcw, ArrowRight } from "lucide-solid";
16
+
17
+ export const OtpVerification01: Component = () => {
18
+ const [code, setCode] = createSignal("");
19
+ const [loading, setLoading] = createSignal(false);
20
+ const [verified, setVerified] = createSignal(false);
21
+ const [countdown, setCountdown] = createSignal(59);
22
+ const [canResend, setCanResend] = createSignal(false);
23
+
24
+ let timerId: ReturnType<typeof setInterval> | undefined;
25
+
26
+ const startCountdown = () => {
27
+ setCountdown(59);
28
+ setCanResend(false);
29
+ clearInterval(timerId);
30
+ timerId = setInterval(() => {
31
+ setCountdown((prev) => {
32
+ if (prev <= 1) {
33
+ clearInterval(timerId);
34
+ setCanResend(true);
35
+ return 0;
36
+ }
37
+ return prev - 1;
38
+ });
39
+ }, 1000);
40
+ };
41
+
42
+ onMount(() => {
43
+ startCountdown();
44
+ });
45
+
46
+ onCleanup(() => {
47
+ clearInterval(timerId);
48
+ });
49
+
50
+ const handleVerify = (e: Event) => {
51
+ e.preventDefault();
52
+ if (code().length < 6) return;
53
+ setLoading(true);
54
+ setTimeout(() => {
55
+ setLoading(false);
56
+ setVerified(true);
57
+ }, 1200);
58
+ };
59
+
60
+ const handleResend = () => {
61
+ if (!canResend()) return;
62
+ setCode("");
63
+ startCountdown();
64
+ };
65
+
66
+ return (
67
+ <div class="@container w-full min-h-[500px] flex items-center justify-center p-4 sm:p-6 md:p-10">
68
+ <Card class="w-full max-w-md border-border shadow-md bg-card">
69
+ {/* Card Header */}
70
+ <CardHeader class="space-y-2 text-center pb-4">
71
+ <div class="mx-auto size-12 rounded-lg bg-primary/10 text-primary flex items-center justify-center mb-1 shadow-2xs">
72
+ <KeyRound class="size-6" />
73
+ </div>
74
+ <div class="flex items-center justify-center gap-2">
75
+ <CardTitle class="text-2xl font-bold tracking-tight">Two-Factor Auth</CardTitle>
76
+ <Badge variant="secondary" class="text-[10px] px-1.5 py-0 font-mono">
77
+ 2FA
78
+ </Badge>
79
+ </div>
80
+ <CardDescription class="text-xs sm:text-sm">
81
+ Enter the 6-digit security code sent to your registered email address
82
+ </CardDescription>
83
+ </CardHeader>
84
+
85
+ <CardContent class="space-y-6">
86
+ {/* Info Alert */}
87
+ <Alert class="bg-muted/40 border-border/70 py-2.5">
88
+ <Mail class="size-4 text-primary shrink-0" />
89
+ <AlertTitle class="text-xs font-semibold">Verification Code Sent</AlertTitle>
90
+ <AlertDescription class="text-[11px] text-muted-foreground">
91
+ Sent to <span class="font-medium text-foreground font-mono">dev***@nikala.dev</span>
92
+ </AlertDescription>
93
+ </Alert>
94
+
95
+ {/* Form */}
96
+ <Form onSubmit={handleVerify} loading={loading()} class="space-y-6">
97
+ {/* Responsive 6-Digit PinInput */}
98
+ <div class="flex flex-col items-center justify-center space-y-2 w-full">
99
+ <PinInput
100
+ value={code()}
101
+ onValueChange={setCode}
102
+ length={6}
103
+ type="numeric"
104
+ class="justify-center items-center gap-1.5 @sm:gap-2.5 max-w-full"
105
+ >
106
+ <PinInputInput index={0} class="h-9 w-9 @sm:h-11 @sm:w-11 text-sm @sm:text-base font-semibold" />
107
+ <PinInputInput index={1} class="h-9 w-9 @sm:h-11 @sm:w-11 text-sm @sm:text-base font-semibold" />
108
+ <PinInputInput index={2} class="h-9 w-9 @sm:h-11 @sm:w-11 text-sm @sm:text-base font-semibold" />
109
+ <PinInputInput index={3} class="h-9 w-9 @sm:h-11 @sm:w-11 text-sm @sm:text-base font-semibold" />
110
+ <PinInputInput index={4} class="h-9 w-9 @sm:h-11 @sm:w-11 text-sm @sm:text-base font-semibold" />
111
+ <PinInputInput index={5} class="h-9 w-9 @sm:h-11 @sm:w-11 text-sm @sm:text-base font-semibold" />
112
+ </PinInput>
113
+
114
+ <Show when={verified()}>
115
+ <div class="flex items-center gap-1.5 text-xs text-emerald-500 font-medium pt-1">
116
+ <CheckCircle2 class="size-3.5" />
117
+ <span>Code verified successfully!</span>
118
+ </div>
119
+ </Show>
120
+ </div>
121
+
122
+ {/* Countdown / Resend Action */}
123
+ <div class="flex items-center justify-between text-xs text-muted-foreground px-1">
124
+ <span>Didn't receive code?</span>
125
+ <Show
126
+ when={canResend()}
127
+ fallback={
128
+ <span class="font-mono text-primary text-[11px]">
129
+ Resend in 00:{countdown() < 10 ? `0${countdown()}` : countdown()}s
130
+ </span>
131
+ }
132
+ >
133
+ <Button
134
+ variant="link"
135
+ size="sm"
136
+ type="button"
137
+ onClick={handleResend}
138
+ class="h-auto p-0 text-xs text-primary font-semibold gap-1"
139
+ >
140
+ <RotateCcw class="size-3" /> Resend Code
141
+ </Button>
142
+ </Show>
143
+ </div>
144
+
145
+ {/* Submit Button */}
146
+ <Button
147
+ type="submit"
148
+ class="w-full font-medium"
149
+ disabled={loading() || code().length < 6 || verified()}
150
+ >
151
+ <Show when={loading()} fallback={<>Verify Account <ArrowRight class="ml-2 size-4" /></>}>
152
+ Verifying...
153
+ </Show>
154
+ </Button>
155
+ </Form>
156
+ </CardContent>
157
+
158
+ {/* Card Footer */}
159
+ <CardFooter class="justify-center border-t border-border/50 py-4 text-xs text-muted-foreground">
160
+ Need help?{" "}
161
+ <a href="#support" class="text-primary font-semibold hover:underline ml-1">
162
+ Contact Security Team
163
+ </a>
164
+ </CardFooter>
165
+ </Card>
166
+ </div>
167
+ );
168
+ };
169
+
170
+ export default OtpVerification01;