@nikala-ui/core 0.10.1-nightly.4e09f2a → 0.11.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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/registry/bubble.json +18 -0
  3. package/registry/create-chat-scroll.json +13 -0
  4. package/registry/create-pagination.json +13 -0
  5. package/registry/footer.json +18 -0
  6. package/registry/forgot-password-01.json +28 -0
  7. package/registry/hero-01.json +22 -0
  8. package/registry/index.json +288 -0
  9. package/registry/login-01.json +28 -0
  10. package/registry/marker.json +17 -0
  11. package/registry/marquee.json +17 -0
  12. package/registry/message.json +17 -0
  13. package/registry/navbar.json +19 -0
  14. package/registry/navigation-menu.json +22 -0
  15. package/registry/otp-verification-01.json +26 -0
  16. package/registry/pagination.json +22 -0
  17. package/registry/rating.json +19 -0
  18. package/registry/register-01.json +31 -0
  19. package/registry/review-card.json +23 -0
  20. package/registry/scroll-area.json +1 -1
  21. package/registry/sidebar.json +24 -0
  22. package/registry/spinner.json +1 -1
  23. package/registry/stat.json +19 -0
  24. package/registry/timeline.json +18 -0
  25. package/registry/toggle-group.json +21 -0
  26. package/src/registry/blocks/forgot-password-01.tsx +141 -0
  27. package/src/registry/blocks/hero-01.tsx +28 -0
  28. package/src/registry/blocks/login-01.tsx +231 -0
  29. package/src/registry/blocks/otp-verification-01.tsx +170 -0
  30. package/src/registry/blocks/register-01.tsx +318 -0
  31. package/src/registry/components/ui/bubble.tsx +142 -0
  32. package/src/registry/components/ui/footer.tsx +247 -0
  33. package/src/registry/components/ui/marker.tsx +102 -0
  34. package/src/registry/components/ui/marquee.tsx +118 -0
  35. package/src/registry/components/ui/message.tsx +168 -0
  36. package/src/registry/components/ui/navbar.tsx +368 -0
  37. package/src/registry/components/ui/navigation-menu.tsx +358 -0
  38. package/src/registry/components/ui/pagination.tsx +258 -0
  39. package/src/registry/components/ui/rating.tsx +185 -0
  40. package/src/registry/components/ui/review-card.tsx +195 -0
  41. package/src/registry/components/ui/scroll-area.tsx +2 -2
  42. package/src/registry/components/ui/sidebar.tsx +692 -0
  43. package/src/registry/components/ui/spinner.tsx +1 -1
  44. package/src/registry/components/ui/stat.tsx +245 -0
  45. package/src/registry/components/ui/timeline.tsx +350 -0
  46. package/src/registry/components/ui/toggle-group.tsx +203 -0
  47. package/src/registry/index.ts +3 -3
  48. package/src/registry/metadata.ts +120 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikala-ui/core",
3
- "version": "0.10.1-nightly.4e09f2a",
3
+ "version": "0.11.0",
4
4
  "description": "Core component definitions, design tokens, and registry for Nikala UI",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "bubble",
3
+ "title": "Bubble",
4
+ "description": "Chat message bubble container supporting variants, grouped consecutive bubbles, and emoji reactions.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/bubble.tsx",
14
+ "content": "import {\n splitProps,\n type Component,\n type JSX,\n type ParentComponent,\n useContext,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- 1. Bubble Variants --- */\nexport const bubbleVariants = cva(\n \"relative max-w-full rounded-lg transition-colors break-words text-left select-text\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground shadow-2xs\",\n muted: \"bg-muted text-foreground border border-border/50 shadow-2xs\",\n outline: \"border border-border bg-background text-foreground shadow-2xs\",\n ghost: \"bg-transparent text-foreground\",\n },\n size: {\n sm: \"px-3 py-1.5 text-xs\",\n default: \"px-4 py-2.5 text-sm\",\n lg: \"px-5 py-3 text-base\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\n/* --- 2. BubbleGroup --- */\nexport interface BubbleGroupProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const BubbleGroup: ParentComponent<BubbleGroupProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"flex flex-col gap-1 w-full\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 3. Bubble --- */\nexport interface BubbleProps\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof bubbleVariants> {\n class?: string;\n}\n\nexport const Bubble: ParentComponent<BubbleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"variant\", \"size\", \"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n bubbleVariants({\n variant: local.variant,\n size: local.size,\n }),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 4. BubbleContent --- */\nexport interface BubbleContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const BubbleContent: ParentComponent<BubbleContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"leading-relaxed\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 5. BubbleReactions --- */\nexport interface BubbleReactionsProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const BubbleReactions: ParentComponent<BubbleReactionsProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"flex items-center gap-1.5 pt-2 mt-2 border-t border-border/30 z-10 flex-wrap\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 6. BubbleReaction --- */\nexport interface BubbleReactionProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {\n active?: boolean;\n count?: number;\n class?: string;\n}\n\nexport const BubbleReaction: ParentComponent<BubbleReactionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"active\", \"count\", \"class\", \"children\"]);\n\n return (\n <button\n type=\"button\"\n data-active={local.active ? \"true\" : \"false\"}\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-md border border-border/70 bg-background/80 px-2 py-1 text-xs text-foreground shadow-2xs transition-all hover:bg-accent hover:border-primary/40 cursor-pointer select-none\",\n local.active && \"border-primary/40 bg-primary/10 text-primary font-medium\",\n local.class\n )}\n {...rest}\n >\n <span class=\"text-xs leading-none\">{local.children}</span>\n {local.count !== undefined && (\n <span class=\"text-[11px] font-mono leading-none text-muted-foreground\">{local.count}</span>\n )}\n </button>\n );\n};\n",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-chat-scroll",
3
+ "title": "createChatScroll",
4
+ "description": "SolidJS reactive primitive for automated chat container scrolling with manual scroll-up detection",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-chat-scroll.ts",
9
+ "content": "import { createSignal, createEffect, onCleanup, onMount, type Accessor } from \"solid-js\";\n\nexport interface CreateChatScrollOptions {\n /** Target scrollable container element or accessor */\n target: HTMLElement | Accessor<HTMLElement | undefined>;\n /** Dependency accessor (e.g. messages length or content signal) that triggers auto-scroll when changed */\n trigger?: Accessor<any>;\n /** Threshold in pixels from bottom to consider the container \"at bottom\". Defaults to 40. */\n threshold?: number;\n /** Whether auto-scroll is enabled. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Scroll behavior: \"smooth\" or \"auto\". Defaults to \"smooth\". */\n behavior?: ScrollBehavior;\n}\n\nexport interface CreateChatScrollReturn {\n /** Accessor indicating whether the container is currently scrolled to the bottom */\n isAtBottom: Accessor<boolean>;\n /** Accessor indicating whether user has manually scrolled up away from bottom */\n isScrolledUp: Accessor<boolean>;\n /** Programmatically scroll container directly to the bottom */\n scrollToBottom: (options?: { smooth?: boolean }) => void;\n}\n\n/**\n * SolidJS reactive primitive for chat and streaming message auto-scrolling with user scroll detection.\n *\n * @param options Chat scroll configuration options.\n */\nexport function createChatScroll(options: CreateChatScrollOptions): CreateChatScrollReturn {\n const [isAtBottom, setIsAtBottom] = createSignal<boolean>(true);\n const isScrolledUp = () => !isAtBottom();\n\n const getElement = (): HTMLElement | undefined => {\n if (typeof options.target === \"function\") {\n return (options.target as Accessor<HTMLElement | undefined>)();\n }\n return options.target;\n };\n\n const isEnabled = () => {\n if (typeof options.enabled === \"function\") {\n return (options.enabled as Accessor<boolean>)();\n }\n return options.enabled ?? true;\n };\n\n const threshold = options.threshold ?? 40;\n\n const checkIfAtBottom = () => {\n const el = getElement();\n if (!el) return true;\n const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n return distanceToBottom <= threshold;\n };\n\n const scrollToBottom = (opts?: { smooth?: boolean }) => {\n const el = getElement();\n if (!el) return;\n\n const useSmooth = opts?.smooth ?? (options.behavior === \"smooth\" || options.behavior === undefined);\n\n el.scrollTo({\n top: el.scrollHeight,\n behavior: useSmooth ? \"smooth\" : \"auto\",\n });\n setIsAtBottom(true);\n };\n\n const handleScroll = () => {\n const atBottom = checkIfAtBottom();\n setIsAtBottom(atBottom);\n };\n\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n const el = getElement();\n if (el) {\n el.addEventListener(\"scroll\", handleScroll, { passive: true });\n setIsAtBottom(checkIfAtBottom());\n }\n });\n\n onCleanup(() => {\n if (typeof window === \"undefined\") return;\n const el = getElement();\n if (el) {\n el.removeEventListener(\"scroll\", handleScroll);\n }\n });\n\n // Watch trigger dependencies (e.g. messages length or stream tokens)\n if (options.trigger) {\n createEffect(() => {\n // Track trigger dependency\n options.trigger!();\n\n if (isEnabled() && isAtBottom()) {\n // Run after microtask/DOM paint\n setTimeout(() => {\n scrollToBottom({ smooth: true });\n }, 10);\n }\n });\n }\n\n return {\n isAtBottom,\n isScrolledUp,\n scrollToBottom,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "create-pagination",
3
+ "title": "createPagination",
4
+ "description": "SolidJS reactive primitive for computing pagination state, dynamic page ranges with ellipses, and navigation controls",
5
+ "type": "registry:hook",
6
+ "files": [
7
+ {
8
+ "path": "hooks/create-pagination.ts",
9
+ "content": "import { createSignal, createMemo, type Accessor } from \"solid-js\";\n\nexport interface CreatePaginationOptions {\n /** Total number of items across all pages, or total count. */\n count?: number | Accessor<number>;\n /** Explicit total number of pages. If provided, overrides count / pageSize calculation. */\n totalPages?: number | Accessor<number>;\n /** Controlled active page number (1-indexed). */\n page?: number | Accessor<number>;\n /** Default active page number for uncontrolled state. Defaults to 1. */\n defaultPage?: number;\n /** Number of items per page. Defaults to 10. */\n pageSize?: number | Accessor<number>;\n /** Number of sibling page buttons visible on each side of the current active page. Defaults to 1. */\n siblingCount?: number | Accessor<number>;\n /** Number of boundary pages visible at the beginning and end. Defaults to 1. */\n boundaries?: number | Accessor<number>;\n /** Callback fired whenever the active page changes. */\n onChange?: (page: number) => void;\n}\n\nexport interface CreatePaginationReturn {\n /** Accessor returning the current active page number (1-indexed). */\n page: Accessor<number>;\n /** Accessor returning the calculated total number of pages. */\n totalPages: Accessor<number>;\n /** Accessor returning the active page size. */\n pageSize: Accessor<number>;\n /** Accessor returning an array of page numbers and \"ellipsis\" strings. */\n range: Accessor<(number | \"ellipsis\")[]>;\n /** Function to programmatically change to a specific page number. */\n setPage: (page: number) => void;\n /** Function to navigate to the next page. */\n next: () => void;\n /** Function to navigate to the previous page. */\n previous: () => void;\n /** Function to navigate to the first page (1). */\n first: () => void;\n /** Function to navigate to the last page (totalPages). */\n last: () => void;\n /** Accessor indicating whether a next page exists. */\n hasNext: Accessor<boolean>;\n /** Accessor indicating whether a previous page exists. */\n hasPrevious: Accessor<boolean>;\n /** 1-based start index of items on the current page (e.g. 1 for page 1 with pageSize 10). */\n startIndex: Accessor<number>;\n /** 1-based end index of items on the current page (e.g. 10 for page 1 with pageSize 10). */\n endIndex: Accessor<number>;\n}\n\n/**\n * SolidJS reactive primitive for computing pagination state, dynamic page range with ellipses, and navigation helpers.\n *\n * @param options Pagination configuration options.\n */\nexport function createPagination(options: CreatePaginationOptions = {}): CreatePaginationReturn {\n const getCount = () => {\n const raw = typeof options.count === \"function\" ? options.count() : options.count ?? 0;\n return Math.max(0, raw);\n };\n\n const getPageSize = () => {\n const raw = typeof options.pageSize === \"function\" ? options.pageSize() : options.pageSize ?? 10;\n return Math.max(1, raw);\n };\n\n const getExplicitTotalPages = () => {\n const raw = typeof options.totalPages === \"function\" ? options.totalPages() : options.totalPages;\n return raw !== undefined ? Math.max(1, raw) : undefined;\n };\n\n const getSiblingCount = () => {\n const raw = typeof options.siblingCount === \"function\" ? options.siblingCount() : options.siblingCount ?? 1;\n return Math.max(0, raw);\n };\n\n const getBoundaries = () => {\n const raw = typeof options.boundaries === \"function\" ? options.boundaries() : options.boundaries ?? 1;\n return Math.max(0, raw);\n };\n\n const [internalPage, setInternalPage] = createSignal<number>(Math.max(1, options.defaultPage ?? 1));\n\n const totalPages = createMemo<number>(() => {\n const explicit = getExplicitTotalPages();\n if (explicit !== undefined) {\n return Math.max(1, explicit);\n }\n const count = getCount();\n const size = getPageSize();\n return Math.max(1, Math.ceil(count / size));\n });\n\n const rawPage = () => {\n if (typeof options.page === \"function\") {\n return options.page();\n }\n if (typeof options.page === \"number\") {\n return options.page;\n }\n return internalPage();\n };\n\n const page = createMemo<number>(() => {\n const p = rawPage();\n const max = totalPages();\n if (p < 1) return 1;\n if (p > max) return max;\n return p;\n });\n\n const setPage = (nextPage: number) => {\n const max = totalPages();\n const clamped = Math.max(1, Math.min(nextPage, max));\n if (typeof options.page !== \"function\" && typeof options.page !== \"number\") {\n setInternalPage(clamped);\n }\n options.onChange?.(clamped);\n };\n\n const next = () => setPage(page() + 1);\n const previous = () => setPage(page() - 1);\n const first = () => setPage(1);\n const last = () => setPage(totalPages());\n\n const hasNext = createMemo(() => page() < totalPages());\n const hasPrevious = createMemo(() => page() > 1);\n\n const startIndex = createMemo(() => {\n const count = getCount();\n if (count === 0 && getExplicitTotalPages() === undefined) return 0;\n return (page() - 1) * getPageSize() + 1;\n });\n\n const endIndex = createMemo(() => {\n const count = getCount();\n const calculated = page() * getPageSize();\n if (count > 0) {\n return Math.min(calculated, count);\n }\n return calculated;\n });\n\n const range = createMemo<(number | \"ellipsis\")[]>(() => {\n const total = totalPages();\n const current = page();\n const siblings = getSiblingCount();\n const boundaries = getBoundaries();\n\n if (total <= 1) {\n return [1];\n }\n\n const pagesSet = new Set<number>();\n\n // 1. Boundary pages at the start\n for (let i = 1; i <= Math.min(boundaries, total); i++) {\n pagesSet.add(i);\n }\n\n // 2. Sibling pages around current page\n const leftSibling = Math.max(1, current - siblings);\n const rightSibling = Math.min(total, current + siblings);\n for (let i = leftSibling; i <= rightSibling; i++) {\n pagesSet.add(i);\n }\n\n // 3. Boundary pages at the end\n for (let i = Math.max(1, total - boundaries + 1); i <= total; i++) {\n pagesSet.add(i);\n }\n\n const sortedPages = Array.from(pagesSet).sort((a, b) => a - b);\n const result: (number | \"ellipsis\")[] = [];\n\n for (let i = 0; i < sortedPages.length; i++) {\n const currentPageNum = sortedPages[i];\n if (i > 0) {\n const prevPageNum = sortedPages[i - 1];\n const gap = currentPageNum - prevPageNum;\n if (gap === 2) {\n result.push(prevPageNum + 1);\n } else if (gap > 2) {\n result.push(\"ellipsis\");\n }\n }\n result.push(currentPageNum);\n }\n\n return result;\n });\n\n return {\n page,\n totalPages,\n pageSize: getPageSize,\n range,\n setPage,\n next,\n previous,\n first,\n last,\n hasNext,\n hasPrevious,\n startIndex,\n endIndex,\n };\n}\n",
10
+ "type": "registry:hook"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "footer",
3
+ "title": "Footer",
4
+ "description": "A responsive, accessible, and structured bottom navigation layout suite supporting multi-column link directories, brand sections, and newsletter inputs.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/footer.tsx",
14
+ "content": "import { splitProps, type JSX, type ParentComponent } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- Root Footer --- */\nexport const footerVariants = cva(\"w-full transition-colors\", {\n variants: {\n variant: {\n default: \"bg-background border-t border-border/60 text-foreground\",\n muted: \"bg-muted/40 border-t border-border text-foreground\",\n bordered: \"bg-background border-t-2 border-border text-foreground\",\n floating: \"my-8 rounded-lg border border-border/80 bg-card text-card-foreground shadow-xs\",\n transparent: \"bg-transparent border-transparent text-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n});\n\nexport interface FooterProps\n extends JSX.HTMLAttributes<HTMLElement>,\n VariantProps<typeof footerVariants> {\n maxWidth?: \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"full\";\n}\n\nexport const Footer: ParentComponent<FooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"variant\", \"maxWidth\", \"children\"]);\n\n const maxWidthClass = () => {\n switch (local.maxWidth) {\n case \"sm\":\n return \"max-w-screen-sm\";\n case \"md\":\n return \"max-w-screen-md\";\n case \"lg\":\n return \"max-w-screen-lg\";\n case \"xl\":\n return \"max-w-screen-xl\";\n case \"full\":\n return \"max-w-full\";\n case \"2xl\":\n default:\n return \"max-w-screen-2xl\";\n }\n };\n\n return (\n <footer\n class={cn(\n footerVariants({ variant: local.variant }),\n local.variant === \"floating\" && cn(\"mx-auto\", maxWidthClass()),\n local.class\n )}\n {...rest}\n >\n <div class={cn(\"w-full\", local.variant !== \"floating\" && cn(\"mx-auto\", maxWidthClass()))}>\n {local.children}\n </div>\n </footer>\n );\n};\n\n/* --- Footer Container --- */\nexport interface FooterContainerProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const FooterContainer: ParentComponent<FooterContainerProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"px-4 sm:px-6 lg:px-8 py-10 sm:py-12 md:py-16 w-full\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- Footer Content (Columns Grid / Layout) --- */\nexport interface FooterContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const FooterContent: ParentComponent<FooterContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"grid grid-cols-2 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-8 lg:gap-12\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- Footer Column --- */\nexport interface FooterColumnProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const FooterColumn: ParentComponent<FooterColumnProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div class={cn(\"flex flex-col space-y-3\", local.class)} {...rest}>\n {local.children}\n </div>\n );\n};\n\n/* --- Footer Column Title --- */\nexport interface FooterColumnTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {\n class?: string;\n}\n\nexport const FooterColumnTitle: ParentComponent<FooterColumnTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <h4\n class={cn(\n \"text-xs font-semibold uppercase tracking-wider text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </h4>\n );\n};\n\n/* --- Footer Column List --- */\nexport interface FooterColumnListProps extends JSX.HTMLAttributes<HTMLUListElement> {\n class?: string;\n}\n\nexport const FooterColumnList: ParentComponent<FooterColumnListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <ul class={cn(\"space-y-2 list-none p-0 m-0\", local.class)} {...rest}>\n {local.children}\n </ul>\n );\n};\n\n/* --- Footer Link --- */\nexport interface FooterLinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {\n class?: string;\n}\n\nexport const FooterLink: ParentComponent<FooterLinkProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <li>\n <a\n class={cn(\n \"inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </a>\n </li>\n );\n};\n\n/* --- Footer Brand / Info Section --- */\nexport interface FooterBrandProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const FooterBrand: ParentComponent<FooterBrandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"col-span-2 sm:col-span-2 md:col-span-4 lg:col-span-2 flex flex-col space-y-3 mb-4 lg:mb-0\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- Footer Bottom (Copyright and Secondary Links Bar) --- */\nexport interface FooterBottomProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const FooterBottom: ParentComponent<FooterBottomProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"mt-12 sm:mt-16 pt-8 border-t border-border/50 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-muted-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- Footer Copyright Text --- */\nexport interface FooterCopyrightProps extends JSX.HTMLAttributes<HTMLParagraphElement> {\n class?: string;\n}\n\nexport const FooterCopyright: ParentComponent<FooterCopyrightProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <p class={cn(\"text-xs text-muted-foreground leading-relaxed\", local.class)} {...rest}>\n {local.children}\n </p>\n );\n};\n\n/* --- Footer Social Links Container --- */\nexport interface FooterSocialsProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const FooterSocials: ParentComponent<FooterSocialsProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div class={cn(\"flex items-center gap-3 text-muted-foreground\", local.class)} {...rest}>\n {local.children}\n </div>\n );\n};\n",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "forgot-password-01",
3
+ "title": "Forgot Password 01 — Account Recovery Flow",
4
+ "description": "A sleek password recovery block with email instructions submission and success confirmation states.",
5
+ "type": "registry:block",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "lucide-solid"
10
+ ],
11
+ "registryDependencies": [
12
+ "card",
13
+ "input",
14
+ "label",
15
+ "button",
16
+ "alert",
17
+ "form",
18
+ "field",
19
+ "form-message"
20
+ ],
21
+ "files": [
22
+ {
23
+ "path": "blocks/forgot-password-01.tsx",
24
+ "content": "import { createSignal, Show, type Component } from \"solid-js\";\nimport { createForm } from \"@nikala-ui/hooks\";\nimport {\n Card,\n CardHeader,\n CardTitle,\n CardDescription,\n CardContent,\n CardFooter,\n} from \"../components/ui/card\";\nimport { Form } from \"../components/ui/form\";\nimport { Field, FieldLabel } from \"../components/ui/field\";\nimport { FormMessage } from \"../components/ui/form-message\";\nimport { Button } from \"../components/ui/button\";\nimport { Input } from \"../components/ui/input\";\nimport { Alert, AlertTitle, AlertDescription } from \"../components/ui/alert\";\nimport { Lock, Mail, ArrowLeft, ArrowRight, CheckCircle2, RotateCcw } from \"lucide-solid\";\n\nexport const ForgotPassword01: Component = () => {\n const [sent, setSent] = createSignal(false);\n const [submittedEmail, setSubmittedEmail] = createSignal(\"\");\n\n const form = createForm({\n initialValues: {\n email: \"\",\n },\n validate: (values) => {\n const errors: Record<string, string> = {};\n if (!values.email.trim()) {\n errors.email = \"Email is required\";\n } else if (!values.email.includes(\"@\")) {\n errors.email = \"Please enter a valid email address\";\n }\n return errors;\n },\n onSubmit: async (values) => {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n setSubmittedEmail(values.email);\n setSent(true);\n },\n });\n\n const isFormValid = () =>\n form.values().email.trim() !== \"\" && form.values().email.includes(\"@\");\n\n const handleReset = () => {\n setSent(false);\n form.resetForm();\n };\n\n return (\n <div class=\"@container w-full min-h-[520px] flex items-center justify-center p-4 sm:p-6 md:p-10\">\n <Card class=\"w-full max-w-md border-border shadow-md bg-card\">\n {/* Card Header */}\n <CardHeader class=\"space-y-2 text-center pb-4\">\n <div class=\"mx-auto size-12 rounded-lg bg-primary/10 text-primary flex items-center justify-center mb-1 shadow-2xs\">\n <Show when={sent()} fallback={<Lock class=\"size-6\" />}>\n <Mail class=\"size-6\" />\n </Show>\n </div>\n <CardTitle class=\"text-2xl font-bold tracking-tight\">\n {sent() ? \"Check your email\" : \"Reset your password\"}\n </CardTitle>\n <CardDescription class=\"text-xs sm:text-sm\">\n {sent()\n ? `We have sent a secure password reset link to ${submittedEmail()}`\n : \"Enter the email associated with your account and we will send you a reset link\"}\n </CardDescription>\n </CardHeader>\n\n <CardContent class=\"space-y-4\">\n <Show\n when={sent()}\n fallback={\n <Form onSubmit={form.handleSubmit} loading={form.isSubmitting()} class=\"space-y-4\">\n <Field>\n <FieldLabel for=\"fp-email\" class=\"text-xs sm:text-sm\">Email address</FieldLabel>\n <Input\n id=\"fp-email\"\n type=\"email\"\n placeholder=\"name@company.com\"\n value={form.values().email}\n onInput={form.handleChange(\"email\")}\n onBlur={form.handleBlur(\"email\")}\n autocomplete=\"email\"\n />\n <FormMessage form={form} name=\"email\" />\n </Field>\n\n <Button\n type=\"submit\"\n class=\"w-full font-medium\"\n disabled={form.isSubmitting() || !isFormValid()}\n >\n <Show when={form.isSubmitting()} fallback={<>Send Reset Link <ArrowRight class=\"ml-2 size-4\" /></>}>\n Sending instructions...\n </Show>\n </Button>\n </Form>\n }\n >\n <div class=\"space-y-4\">\n <Alert class=\"bg-emerald-500/10 border-emerald-500/30 text-emerald-500 py-3\">\n <CheckCircle2 class=\"size-4 text-emerald-500 shrink-0\" />\n <AlertTitle class=\"text-xs font-semibold text-foreground\">Instructions Sent</AlertTitle>\n <AlertDescription class=\"text-[11px] text-muted-foreground\">\n If an account exists for <span class=\"font-medium text-foreground font-mono\">{submittedEmail()}</span>, you will receive an email shortly.\n </AlertDescription>\n </Alert>\n\n <div class=\"flex items-center justify-between text-xs text-muted-foreground pt-1\">\n <span>Didn't get the email?</span>\n <Button\n variant=\"link\"\n size=\"sm\"\n type=\"button\"\n onClick={handleReset}\n class=\"h-auto p-0 text-xs text-primary font-semibold gap-1 cursor-pointer\"\n >\n <RotateCcw class=\"size-3\" /> Try another email\n </Button>\n </div>\n </div>\n </Show>\n </CardContent>\n\n {/* Card Footer */}\n <CardFooter class=\"justify-center border-t border-border/50 py-4 text-xs text-muted-foreground\">\n <a\n href=\"/blocks/login-01\"\n class=\"inline-flex items-center gap-1.5 text-foreground hover:text-primary transition-colors font-medium\"\n >\n <ArrowLeft class=\"size-3.5\" /> Back to sign in\n </a>\n </CardFooter>\n </Card>\n </div>\n );\n};\n\nexport default ForgotPassword01;\n",
25
+ "type": "registry:block"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "hero-01",
3
+ "title": "Hero 01 — Simple Centered with Actions",
4
+ "description": "A clean centered hero section with badge pill, high-contrast headline, and dual CTA buttons.",
5
+ "type": "registry:block",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "lucide-solid"
10
+ ],
11
+ "registryDependencies": [
12
+ "button",
13
+ "badge"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "blocks/hero-01.tsx",
18
+ "content": "import { Component } from \"solid-js\";\nimport { Button } from \"../components/ui/button\";\nimport { Badge } from \"../components/ui/badge\";\nimport { ArrowRight } from \"lucide-solid\";\n\nexport default function Hero01() {\n return (\n <section class=\"w-full py-12 md:py-24 lg:py-32 flex flex-col items-center justify-center text-center\">\n <Badge variant=\"secondary\" class=\"mb-4\">\n Nikala UI Blocks\n </Badge>\n <h1 class=\"text-4xl font-bold tracking-tight sm:text-6xl text-foreground max-w-3xl\">\n Build faster with copy-paste SolidJS blocks\n </h1>\n <p class=\"mt-6 text-lg text-muted-foreground max-w-2xl\">\n Pre-designed, fully responsive marketing and application blocks built natively for Tailwind CSS v4.\n </p>\n <div class=\"mt-8 flex items-center gap-4\">\n <Button size=\"lg\">\n Get Started <ArrowRight class=\"ml-2 size-4\" />\n </Button>\n <Button variant=\"outline\" size=\"lg\">\n Documentation\n </Button>\n </div>\n </section>\n );\n}\n",
19
+ "type": "registry:block"
20
+ }
21
+ ]
22
+ }
@@ -74,6 +74,17 @@
74
74
  "tailwind-merge"
75
75
  ]
76
76
  },
77
+ {
78
+ "name": "bubble",
79
+ "title": "Bubble",
80
+ "description": "Chat message bubble container supporting variants, grouped consecutive bubbles, and emoji reactions.",
81
+ "type": "registry:ui",
82
+ "dependencies": [
83
+ "clsx",
84
+ "tailwind-merge",
85
+ "class-variance-authority"
86
+ ]
87
+ },
77
88
  {
78
89
  "name": "button-group",
79
90
  "title": "Button Group",
@@ -247,6 +258,17 @@
247
258
  "label"
248
259
  ]
249
260
  },
261
+ {
262
+ "name": "footer",
263
+ "title": "Footer",
264
+ "description": "A responsive, accessible, and structured bottom navigation layout suite supporting multi-column link directories, brand sections, and newsletter inputs.",
265
+ "type": "registry:ui",
266
+ "dependencies": [
267
+ "clsx",
268
+ "tailwind-merge",
269
+ "class-variance-authority"
270
+ ]
271
+ },
250
272
  {
251
273
  "name": "form-message",
252
274
  "title": "Form Message",
@@ -359,6 +381,63 @@
359
381
  "tailwind-merge"
360
382
  ]
361
383
  },
384
+ {
385
+ "name": "marker",
386
+ "title": "Marker",
387
+ "description": "System chat events, date dividers, and live typing indicator badges.",
388
+ "type": "registry:ui",
389
+ "dependencies": [
390
+ "clsx",
391
+ "tailwind-merge"
392
+ ]
393
+ },
394
+ {
395
+ "name": "marquee",
396
+ "title": "Marquee",
397
+ "description": "A smooth, GPU-accelerated infinite scrolling ticker component for logo clouds, testimonials, and live ribbons.",
398
+ "type": "registry:ui",
399
+ "dependencies": [
400
+ "clsx",
401
+ "tailwind-merge"
402
+ ]
403
+ },
404
+ {
405
+ "name": "message",
406
+ "title": "Message",
407
+ "description": "A structured chat and conversation message layout with avatars, alignment, headers, footers, and actions.",
408
+ "type": "registry:ui",
409
+ "dependencies": [
410
+ "clsx",
411
+ "tailwind-merge"
412
+ ]
413
+ },
414
+ {
415
+ "name": "navbar",
416
+ "title": "Navbar",
417
+ "description": "A responsive, accessible, and composable top navigation header suite supporting nested dropdown flyouts, floating card containers, and mobile navigation drawers.",
418
+ "type": "registry:ui",
419
+ "dependencies": [
420
+ "clsx",
421
+ "tailwind-merge",
422
+ "class-variance-authority",
423
+ "lucide-solid"
424
+ ]
425
+ },
426
+ {
427
+ "name": "navigation-menu",
428
+ "title": "Navigation Menu",
429
+ "description": "A responsive and accessible top header navigation menu with mega-menu dropdowns and link previews.",
430
+ "type": "registry:ui",
431
+ "dependencies": [
432
+ "clsx",
433
+ "tailwind-merge",
434
+ "class-variance-authority",
435
+ "lucide-solid"
436
+ ],
437
+ "registryDependencies": [
438
+ "create-click-outside"
439
+ ]
440
+ },
362
441
  {
363
442
  "name": "number-input",
364
443
  "title": "Number Input",
@@ -376,6 +455,21 @@
376
455
  "create-long-press"
377
456
  ]
378
457
  },
458
+ {
459
+ "name": "pagination",
460
+ "title": "Pagination",
461
+ "description": "An accessible multi-page navigation bar with previous, next, page numbers, and ellipsis controls.",
462
+ "type": "registry:ui",
463
+ "dependencies": [
464
+ "clsx",
465
+ "tailwind-merge",
466
+ "class-variance-authority",
467
+ "lucide-solid"
468
+ ],
469
+ "registryDependencies": [
470
+ "create-pagination"
471
+ ]
472
+ },
379
473
  {
380
474
  "name": "pin-input",
381
475
  "title": "Pin Input",
@@ -420,6 +514,18 @@
420
514
  "@kobalte/core"
421
515
  ]
422
516
  },
517
+ {
518
+ "name": "rating",
519
+ "title": "Rating",
520
+ "description": "An accessible star rating component supporting interactive inputs, hover preview states, and read-only score badges.",
521
+ "type": "registry:ui",
522
+ "dependencies": [
523
+ "clsx",
524
+ "tailwind-merge",
525
+ "class-variance-authority",
526
+ "lucide-solid"
527
+ ]
528
+ },
423
529
  {
424
530
  "name": "resizable",
425
531
  "title": "Resizable",
@@ -434,6 +540,22 @@
434
540
  "create-resize-observer"
435
541
  ]
436
542
  },
543
+ {
544
+ "name": "review-card",
545
+ "title": "Review Card",
546
+ "description": "A versatile, structured card component for customer testimonials, product ratings, verified buyer badges, and social proof.",
547
+ "type": "registry:ui",
548
+ "dependencies": [
549
+ "clsx",
550
+ "tailwind-merge",
551
+ "class-variance-authority",
552
+ "lucide-solid"
553
+ ],
554
+ "registryDependencies": [
555
+ "avatar",
556
+ "rating"
557
+ ]
558
+ },
437
559
  {
438
560
  "name": "scroll-area",
439
561
  "title": "Scroll Area",
@@ -487,6 +609,23 @@
487
609
  "scroll-area"
488
610
  ]
489
611
  },
612
+ {
613
+ "name": "sidebar",
614
+ "title": "Sidebar",
615
+ "description": "A composable, collapsible, and accessible application sidebar navigation suite with icon mode, mobile drawer, and keyboard shortcuts.",
616
+ "type": "registry:ui",
617
+ "dependencies": [
618
+ "clsx",
619
+ "tailwind-merge",
620
+ "class-variance-authority",
621
+ "lucide-solid"
622
+ ],
623
+ "registryDependencies": [
624
+ "tooltip",
625
+ "skeleton",
626
+ "separator"
627
+ ]
628
+ },
490
629
  {
491
630
  "name": "skeleton",
492
631
  "title": "Skeleton",
@@ -519,6 +658,18 @@
519
658
  "class-variance-authority"
520
659
  ]
521
660
  },
661
+ {
662
+ "name": "stat",
663
+ "title": "Stat",
664
+ "description": "Display key performance indicators, statistics, financial data, and metrics with trends and icons.",
665
+ "type": "registry:ui",
666
+ "dependencies": [
667
+ "clsx",
668
+ "tailwind-merge",
669
+ "class-variance-authority",
670
+ "lucide-solid"
671
+ ]
672
+ },
522
673
  {
523
674
  "name": "status",
524
675
  "title": "Status",
@@ -586,6 +737,17 @@
586
737
  "dropdown-menu"
587
738
  ]
588
739
  },
740
+ {
741
+ "name": "timeline",
742
+ "title": "Timeline",
743
+ "description": "A responsive chronological display for event streams, activity logs, order tracking, and multi-step workflows.",
744
+ "type": "registry:ui",
745
+ "dependencies": [
746
+ "clsx",
747
+ "tailwind-merge",
748
+ "class-variance-authority"
749
+ ]
750
+ },
589
751
  {
590
752
  "name": "toast",
591
753
  "title": "Toast / Sonner",
@@ -599,6 +761,20 @@
599
761
  "@kobalte/core"
600
762
  ]
601
763
  },
764
+ {
765
+ "name": "toggle-group",
766
+ "title": "Toggle Group",
767
+ "description": "A set of two-state buttons that can be toggled on or off with single or multiple selection modes.",
768
+ "type": "registry:ui",
769
+ "dependencies": [
770
+ "clsx",
771
+ "tailwind-merge",
772
+ "class-variance-authority"
773
+ ],
774
+ "registryDependencies": [
775
+ "create-controllable-signal"
776
+ ]
777
+ },
602
778
  {
603
779
  "name": "toggle",
604
780
  "title": "Toggle",
@@ -621,6 +797,106 @@
621
797
  "@kobalte/core"
622
798
  ]
623
799
  },
800
+ {
801
+ "name": "forgot-password-01",
802
+ "title": "Forgot Password 01 — Account Recovery Flow",
803
+ "description": "A sleek password recovery block with email instructions submission and success confirmation states.",
804
+ "type": "registry:block",
805
+ "dependencies": [
806
+ "clsx",
807
+ "tailwind-merge",
808
+ "lucide-solid"
809
+ ],
810
+ "registryDependencies": [
811
+ "card",
812
+ "input",
813
+ "label",
814
+ "button",
815
+ "alert",
816
+ "form",
817
+ "field",
818
+ "form-message"
819
+ ]
820
+ },
821
+ {
822
+ "name": "hero-01",
823
+ "title": "Hero 01 — Simple Centered with Actions",
824
+ "description": "A clean centered hero section with badge pill, high-contrast headline, and dual CTA buttons.",
825
+ "type": "registry:block",
826
+ "dependencies": [
827
+ "clsx",
828
+ "tailwind-merge",
829
+ "lucide-solid"
830
+ ],
831
+ "registryDependencies": [
832
+ "button",
833
+ "badge"
834
+ ]
835
+ },
836
+ {
837
+ "name": "login-01",
838
+ "title": "Login 01 — Split Screen with Social Auth & Testimonial",
839
+ "description": "A modern split-screen authentication page block with OAuth providers, email sign-in form, and brand testimonial visual.",
840
+ "type": "registry:block",
841
+ "dependencies": [
842
+ "clsx",
843
+ "tailwind-merge",
844
+ "lucide-solid"
845
+ ],
846
+ "registryDependencies": [
847
+ "button",
848
+ "input",
849
+ "label",
850
+ "checkbox",
851
+ "separator",
852
+ "form",
853
+ "field",
854
+ "form-message"
855
+ ]
856
+ },
857
+ {
858
+ "name": "otp-verification-01",
859
+ "title": "OTP Verification 01 — Two-Factor Security Code",
860
+ "description": "A clean 2-Factor Authentication block with a 6-digit PIN input, countdown resend timer, and security notifications.",
861
+ "type": "registry:block",
862
+ "dependencies": [
863
+ "clsx",
864
+ "tailwind-merge",
865
+ "lucide-solid"
866
+ ],
867
+ "registryDependencies": [
868
+ "card",
869
+ "pin-input",
870
+ "button",
871
+ "alert",
872
+ "badge",
873
+ "form"
874
+ ]
875
+ },
876
+ {
877
+ "name": "register-01",
878
+ "title": "Register 01 — Sign-Up Card with Password Strength",
879
+ "description": "A comprehensive sign-up card featuring social logins, live password strength meter with validation checklist, and terms agreement.",
880
+ "type": "registry:block",
881
+ "dependencies": [
882
+ "clsx",
883
+ "tailwind-merge",
884
+ "lucide-solid"
885
+ ],
886
+ "registryDependencies": [
887
+ "card",
888
+ "input",
889
+ "label",
890
+ "button",
891
+ "checkbox",
892
+ "progress",
893
+ "badge",
894
+ "separator",
895
+ "form",
896
+ "field",
897
+ "form-message"
898
+ ]
899
+ },
624
900
  {
625
901
  "name": "create-controllable-signal",
626
902
  "title": "createControllableSignal",
@@ -866,5 +1142,17 @@
866
1142
  "title": "createDropZone",
867
1143
  "description": "SolidJS reactive primitive for file drag & drop operations, validation, and file chooser dialogs",
868
1144
  "type": "registry:hook"
1145
+ },
1146
+ {
1147
+ "name": "create-pagination",
1148
+ "title": "createPagination",
1149
+ "description": "SolidJS reactive primitive for computing pagination state, dynamic page ranges with ellipses, and navigation controls",
1150
+ "type": "registry:hook"
1151
+ },
1152
+ {
1153
+ "name": "create-chat-scroll",
1154
+ "title": "createChatScroll",
1155
+ "description": "SolidJS reactive primitive for automated chat container scrolling with manual scroll-up detection",
1156
+ "type": "registry:hook"
869
1157
  }
870
1158
  ]
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "login-01",
3
+ "title": "Login 01 — Split Screen with Social Auth & Testimonial",
4
+ "description": "A modern split-screen authentication page block with OAuth providers, email sign-in form, and brand testimonial visual.",
5
+ "type": "registry:block",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "lucide-solid"
10
+ ],
11
+ "registryDependencies": [
12
+ "button",
13
+ "input",
14
+ "label",
15
+ "checkbox",
16
+ "separator",
17
+ "form",
18
+ "field",
19
+ "form-message"
20
+ ],
21
+ "files": [
22
+ {
23
+ "path": "blocks/login-01.tsx",
24
+ "content": "import { createSignal, Show, type Component } from \"solid-js\";\nimport { createForm } from \"@nikala-ui/hooks\";\nimport { Form } from \"../components/ui/form\";\nimport { Field, FieldLabel } from \"../components/ui/field\";\nimport { FormMessage } from \"../components/ui/form-message\";\nimport { Button } from \"../components/ui/button\";\nimport { Input } from \"../components/ui/input\";\nimport { Checkbox } from \"../components/ui/checkbox\";\nimport { Label } from \"../components/ui/label\";\nimport { Separator } from \"../components/ui/separator\";\nimport { Eye, EyeOff, Sparkles, ArrowRight } from \"lucide-solid\";\n\nexport const Login01: Component = () => {\n const [showPassword, setShowPassword] = createSignal(false);\n\n const form = createForm({\n initialValues: {\n email: \"\",\n password: \"\",\n rememberMe: true,\n },\n validate: (values) => {\n const errors: Record<string, string> = {};\n if (!values.email) {\n errors.email = \"Email is required\";\n } else if (!values.email.includes(\"@\")) {\n errors.email = \"Please enter a valid email address\";\n }\n if (!values.password) {\n errors.password = \"Password is required\";\n }\n return errors;\n },\n onSubmit: async (values) => {\n // Simulate API request\n await new Promise((resolve) => setTimeout(resolve, 1200));\n },\n });\n\n const isFormValid = () =>\n form.values().email.trim() !== \"\" &&\n form.values().email.includes(\"@\") &&\n form.values().password.trim() !== \"\";\n\n return (\n <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\">\n {/* Left Column: Login Form */}\n <div class=\"flex flex-col justify-between p-6 @md:p-10 @5xl:p-12 w-full\">\n {/* Brand Header */}\n <div class=\"flex items-center gap-2.5\">\n <div class=\"size-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm shadow-2xs shrink-0\">\n N\n </div>\n <span class=\"font-bold text-lg tracking-tight\">Nikala UI</span>\n </div>\n\n {/* Form Container */}\n <div class=\"mx-auto w-full max-w-sm my-auto py-6 @md:py-8\">\n <div class=\"space-y-1.5 text-left mb-6\">\n <h1 class=\"text-2xl @md:text-3xl font-bold tracking-tight text-foreground\">\n Welcome back\n </h1>\n <p class=\"text-xs @md:text-sm text-muted-foreground\">\n Enter your credentials to access your account dashboard\n </p>\n </div>\n\n {/* Social OAuth Buttons */}\n <div class=\"grid grid-cols-1 @xs:grid-cols-2 gap-2.5 mb-6\">\n <Button variant=\"outline\" class=\"w-full justify-center text-xs @md:text-sm h-9\">\n <svg class=\"size-4 mr-2 shrink-0\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <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\" />\n </svg>\n GitHub\n </Button>\n <Button variant=\"outline\" class=\"w-full justify-center text-xs @md:text-sm h-9\">\n <svg class=\"size-4 mr-2 shrink-0\" viewBox=\"0 0 24 24\">\n <path\n fill=\"#4285F4\"\n 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\"\n />\n <path\n fill=\"#34A853\"\n 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\"\n />\n <path\n fill=\"#FBBC05\"\n 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\"\n />\n <path\n fill=\"#EA4335\"\n 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\"\n />\n </svg>\n Google\n </Button>\n </div>\n\n <div class=\"relative mb-6\">\n <div class=\"absolute inset-0 flex items-center\">\n <Separator />\n </div>\n <div class=\"relative flex justify-center text-xs uppercase\">\n <span class=\"bg-background px-3 text-muted-foreground font-medium text-[11px]\">\n Or continue with email\n </span>\n </div>\n </div>\n\n {/* Form with Nikala UI Form & Field ecosystem */}\n <Form onSubmit={form.handleSubmit} loading={form.isSubmitting()} class=\"space-y-4\">\n <Field>\n <FieldLabel for=\"login-email\" class=\"text-xs @md:text-sm\">Email address</FieldLabel>\n <Input\n id=\"login-email\"\n type=\"email\"\n placeholder=\"name@company.com\"\n value={form.values().email}\n onInput={form.handleChange(\"email\")}\n onBlur={form.handleBlur(\"email\")}\n autocomplete=\"email\"\n />\n <FormMessage form={form} name=\"email\" />\n </Field>\n\n <Field>\n <div class=\"flex items-center justify-between\">\n <FieldLabel for=\"login-password\" class=\"text-xs @md:text-sm\">Password</FieldLabel>\n <a\n href=\"/blocks/forgot-password-01\"\n class=\"text-[11px] @md:text-xs text-primary font-medium hover:underline\"\n >\n Forgot password?\n </a>\n </div>\n <div class=\"relative\">\n <Input\n id=\"login-password\"\n type={showPassword() ? \"text\" : \"password\"}\n placeholder=\"••••••••\"\n value={form.values().password}\n onInput={form.handleChange(\"password\")}\n onBlur={form.handleBlur(\"password\")}\n autocomplete=\"current-password\"\n class=\"pr-10\"\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword())}\n class=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors p-1 cursor-pointer\"\n aria-label={showPassword() ? \"Hide password\" : \"Show password\"}\n >\n <Show when={showPassword()} fallback={<Eye class=\"size-4\" />}>\n <EyeOff class=\"size-4\" />\n </Show>\n </button>\n </div>\n <FormMessage form={form} name=\"password\" />\n </Field>\n\n <div class=\"flex items-center space-x-2 pt-1\">\n <Checkbox\n id=\"remember\"\n checked={form.values().rememberMe}\n onChange={(checked) => form.setFieldValue(\"rememberMe\", checked)}\n />\n <Label\n for=\"remember\"\n class=\"text-xs font-normal text-muted-foreground cursor-pointer select-none\"\n >\n Remember me for 30 days\n </Label>\n </div>\n\n <Button\n type=\"submit\"\n class=\"w-full mt-2 font-medium\"\n disabled={form.isSubmitting() || !isFormValid()}\n >\n <Show when={form.isSubmitting()} fallback={<>Sign In <ArrowRight class=\"ml-2 size-4\" /></>}>\n Signing in...\n </Show>\n </Button>\n </Form>\n\n <p class=\"text-center text-xs text-muted-foreground mt-6\">\n Don't have an account?{\" \"}\n <a href=\"/blocks/register-01\" class=\"text-primary font-semibold hover:underline\">\n Sign up\n </a>\n </p>\n </div>\n\n {/* Footer info */}\n <div class=\"text-center @md:text-left text-xs text-muted-foreground pt-4 @md:pt-0\">\n By signing in, you agree to our{\" \"}\n <a href=\"#terms\" class=\"underline hover:text-foreground\">Terms of Service</a>{\" \"}\n and{\" \"}\n <a href=\"#privacy\" class=\"underline hover:text-foreground\">Privacy Policy</a>.\n </div>\n </div>\n\n {/* Right Column: Hero Visual & Art / Testimonial Showcase */}\n <div class=\"hidden @5xl:flex relative flex-col justify-between p-10 bg-muted/40 border-l border-border overflow-hidden\">\n <div class=\"absolute inset-0 bg-gradient-to-tr from-primary/10 via-transparent to-primary/5 pointer-events-none\" />\n\n <div class=\"relative z-10 flex items-center gap-2 text-sm font-semibold text-foreground/80\">\n <Sparkles class=\"size-4 text-primary\" />\n <span>Production Ready SolidJS UI</span>\n </div>\n\n <div class=\"relative z-10 max-w-md space-y-6\">\n <blockquote class=\"text-xl @md:text-2xl font-semibold tracking-tight text-foreground leading-snug\">\n \"Nikala UI transformed the way our team builds web applications with SolidJS. Fine-grained reactivity meets Tailwind v4 simplicity.\"\n </blockquote>\n <div class=\"space-y-1\">\n <p class=\"text-sm font-semibold text-foreground\">Davit Kakhidze</p>\n <p class=\"text-xs text-muted-foreground\">Head of Engineering at Studio</p>\n </div>\n </div>\n\n <div class=\"relative z-10 flex items-center justify-between text-xs text-muted-foreground\">\n <span>Honoring Niko Pirosmani</span>\n <span>© 2026 Nikala UI</span>\n </div>\n </div>\n </div>\n );\n};\n\nexport default Login01;\n",
25
+ "type": "registry:block"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "marker",
3
+ "title": "Marker",
4
+ "description": "System chat events, date dividers, and live typing indicator badges.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/marker.tsx",
13
+ "content": "import {\n splitProps,\n type Component,\n type JSX,\n type ParentComponent,\n Show,\n} from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- 1. Marker Root --- */\nexport interface MarkerProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const Marker: ParentComponent<MarkerProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n role=\"status\"\n class={cn(\"flex w-full items-center justify-center my-3 text-center select-none\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 2. MarkerContent --- */\nexport interface MarkerContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const MarkerContent: ParentComponent<MarkerContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-lg border border-border/60 bg-muted/40 px-3 py-1 text-xs text-muted-foreground shadow-2xs backdrop-blur-xs\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 3. MarkerDate (Date Divider) --- */\nexport interface MarkerDateProps extends JSX.HTMLAttributes<HTMLDivElement> {\n date?: string;\n class?: string;\n}\n\nexport const MarkerDate: ParentComponent<MarkerDateProps> = (props) => {\n const [local, rest] = splitProps(props, [\"date\", \"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"relative flex w-full items-center justify-center my-4\", local.class)}\n {...rest}\n >\n <div class=\"absolute inset-0 flex items-center\">\n <div class=\"w-full border-t border-border/50\" />\n </div>\n <div class=\"relative flex items-center gap-1 bg-background px-3 py-0.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground rounded-md border border-border/40\">\n {local.children || local.date}\n </div>\n </div>\n );\n};\n\n/* --- 4. MarkerTyping (Live Typing Indicator) --- */\nexport interface MarkerTypingProps extends JSX.HTMLAttributes<HTMLDivElement> {\n name?: string;\n class?: string;\n}\n\nexport const MarkerTyping: Component<MarkerTypingProps> = (props) => {\n const [local, rest] = splitProps(props, [\"name\", \"class\"]);\n\n return (\n <div\n role=\"status\"\n aria-label={`${local.name || \"Someone\"} is typing`}\n class={cn(\"flex w-full items-center gap-2 text-xs text-muted-foreground my-2\", local.class)}\n {...rest}\n >\n <Show when={local.name}>\n <span class=\"font-medium text-foreground\">{local.name}</span> is typing\n </Show>\n\n {/* 3 Animated Bouncing Dots */}\n <span class=\"inline-flex items-center gap-1 px-2 py-1 rounded-md bg-muted/60 border border-border/40\">\n <span class=\"size-1.5 rounded-lg bg-foreground/60 animate-bounce [animation-delay:-0.3s]\" />\n <span class=\"size-1.5 rounded-lg bg-foreground/60 animate-bounce [animation-delay:-0.15s]\" />\n <span class=\"size-1.5 rounded-lg bg-foreground/60 animate-bounce\" />\n </span>\n </div>\n );\n};\n",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }