@zentauri-ui/zentauri-components 2.2.2 → 2.3.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 (95) hide show
  1. package/README.md +7 -5
  2. package/cli/props.json +140 -0
  3. package/cli/registry.json +3 -0
  4. package/dist/{chunk-5I4GAURE.js → chunk-AARJLZXP.js} +6 -6
  5. package/dist/{chunk-5I4GAURE.js.map → chunk-AARJLZXP.js.map} +1 -1
  6. package/dist/{chunk-N2G7IWHS.mjs → chunk-BFHJF4MV.mjs} +4 -4
  7. package/dist/{chunk-N2G7IWHS.mjs.map → chunk-BFHJF4MV.mjs.map} +1 -1
  8. package/dist/{chunk-IY72Z65Z.js → chunk-DSX6RUYI.js} +12 -12
  9. package/dist/{chunk-IY72Z65Z.js.map → chunk-DSX6RUYI.js.map} +1 -1
  10. package/dist/chunk-ENKXB2BA.js +19 -0
  11. package/dist/{chunk-ILPPXWR3.js.map → chunk-ENKXB2BA.js.map} +1 -1
  12. package/dist/chunk-EZNR7VLJ.js +65 -0
  13. package/dist/chunk-EZNR7VLJ.js.map +1 -0
  14. package/dist/{chunk-W5MTZJPE.mjs → chunk-JKKF5DCF.mjs} +3 -3
  15. package/dist/{chunk-W5MTZJPE.mjs.map → chunk-JKKF5DCF.mjs.map} +1 -1
  16. package/dist/chunk-RDYR4DHG.mjs +62 -0
  17. package/dist/chunk-RDYR4DHG.mjs.map +1 -0
  18. package/dist/chunk-RWF3NVZP.mjs +29 -0
  19. package/dist/chunk-RWF3NVZP.mjs.map +1 -0
  20. package/dist/{chunk-NUV2I337.mjs → chunk-WZY32L6K.mjs} +3 -3
  21. package/dist/{chunk-NUV2I337.mjs.map → chunk-WZY32L6K.mjs.map} +1 -1
  22. package/dist/chunk-YRQN3AV4.js +38 -0
  23. package/dist/chunk-YRQN3AV4.js.map +1 -0
  24. package/dist/{chunk-UZ6Y5CSV.js → chunk-YY7G4NV3.js} +14 -5
  25. package/dist/chunk-YY7G4NV3.js.map +1 -0
  26. package/dist/{chunk-GFE6ZX5Y.mjs → chunk-ZB6C6CJQ.mjs} +14 -5
  27. package/dist/chunk-ZB6C6CJQ.mjs.map +1 -0
  28. package/dist/design-system/facade.js +6 -5
  29. package/dist/design-system/facade.js.map +1 -1
  30. package/dist/design-system/facade.mjs +5 -4
  31. package/dist/design-system/facade.mjs.map +1 -1
  32. package/dist/design-system/hash-generator.d.ts +15 -0
  33. package/dist/design-system/hash-generator.d.ts.map +1 -0
  34. package/dist/design-system/index.d.ts +1 -0
  35. package/dist/design-system/index.d.ts.map +1 -1
  36. package/dist/hooks/index.d.ts +1 -0
  37. package/dist/hooks/index.d.ts.map +1 -1
  38. package/dist/hooks/useHash/index.d.ts +2 -0
  39. package/dist/hooks/useHash/index.d.ts.map +1 -0
  40. package/dist/hooks/useHash/useHash.d.ts +20 -0
  41. package/dist/hooks/useHash/useHash.d.ts.map +1 -0
  42. package/dist/hooks/useHash.js +18 -0
  43. package/dist/hooks/useHash.js.map +1 -0
  44. package/dist/hooks/useHash.mjs +5 -0
  45. package/dist/hooks/useHash.mjs.map +1 -0
  46. package/dist/ui/buttons/animated.js +8 -7
  47. package/dist/ui/buttons/animated.js.map +1 -1
  48. package/dist/ui/buttons/animated.mjs +6 -5
  49. package/dist/ui/buttons/animated.mjs.map +1 -1
  50. package/dist/ui/buttons.js +9 -8
  51. package/dist/ui/buttons.mjs +7 -6
  52. package/dist/ui/data-table.js +19 -18
  53. package/dist/ui/data-table.js.map +1 -1
  54. package/dist/ui/data-table.mjs +9 -8
  55. package/dist/ui/data-table.mjs.map +1 -1
  56. package/dist/ui/dynamic-stepper.js +18 -17
  57. package/dist/ui/dynamic-stepper.js.map +1 -1
  58. package/dist/ui/dynamic-stepper.mjs +7 -6
  59. package/dist/ui/dynamic-stepper.mjs.map +1 -1
  60. package/dist/ui/hash-generator/hash-generator-base.d.ts +6 -0
  61. package/dist/ui/hash-generator/hash-generator-base.d.ts.map +1 -0
  62. package/dist/ui/hash-generator/hash-generator.d.ts +2 -0
  63. package/dist/ui/hash-generator/hash-generator.d.ts.map +1 -0
  64. package/dist/ui/hash-generator/index.d.ts +5 -0
  65. package/dist/ui/hash-generator/index.d.ts.map +1 -0
  66. package/dist/ui/hash-generator/types.d.ts +17 -0
  67. package/dist/ui/hash-generator/types.d.ts.map +1 -0
  68. package/dist/ui/hash-generator/variants.d.ts +10 -0
  69. package/dist/ui/hash-generator/variants.d.ts.map +1 -0
  70. package/dist/ui/hash-generator.js +126 -0
  71. package/dist/ui/hash-generator.js.map +1 -0
  72. package/dist/ui/hash-generator.mjs +117 -0
  73. package/dist/ui/hash-generator.mjs.map +1 -0
  74. package/dist/ui/pagination.js +10 -9
  75. package/dist/ui/pagination.mjs +7 -6
  76. package/dist/ui/split-button.js +20 -19
  77. package/dist/ui/split-button.js.map +1 -1
  78. package/dist/ui/split-button.mjs +7 -6
  79. package/dist/ui/split-button.mjs.map +1 -1
  80. package/package.json +1 -1
  81. package/src/design-system/hash-generator.ts +34 -0
  82. package/src/design-system/index.ts +1 -0
  83. package/src/hooks/index.ts +6 -0
  84. package/src/hooks/useHash/index.ts +6 -0
  85. package/src/hooks/useHash/useHash.test.ts +77 -0
  86. package/src/hooks/useHash/useHash.ts +89 -0
  87. package/src/ui/hash-generator/hash-generator-base.tsx +106 -0
  88. package/src/ui/hash-generator/hash-generator.test.tsx +73 -0
  89. package/src/ui/hash-generator/hash-generator.tsx +1 -0
  90. package/src/ui/hash-generator/index.ts +18 -0
  91. package/src/ui/hash-generator/types.ts +29 -0
  92. package/src/ui/hash-generator/variants.ts +31 -0
  93. package/dist/chunk-GFE6ZX5Y.mjs.map +0 -1
  94. package/dist/chunk-ILPPXWR3.js +0 -19
  95. package/dist/chunk-UZ6Y5CSV.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ui/split-button/variants.ts","../../src/ui/split-button/split-button-base.tsx","../../src/ui/split-button/split-button.tsx"],"names":["jsx"],"mappings":"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","file":"split-button.mjs","sourcesContent":["import { cva } from \"class-variance-authority\";\n\nimport {\n zuiSplitButtonContent,\n zuiSplitButtonDropdown,\n zuiSplitButtonFullWidth,\n zuiSplitButtonGroup,\n zuiSplitButtonItemDisabled,\n zuiSplitButtonPrimary,\n zuiSplitButtonRoot,\n zuiSplitButtonTrigger,\n zuiSplitButtonTriggerSizes,\n} from \"../../design-system\";\n\nexport const splitButtonRootVariants = cva(zuiSplitButtonRoot, {\n variants: {\n fullWidth: {\n true: zuiSplitButtonFullWidth,\n },\n },\n});\nexport const splitButtonDropdownVariants = cva(zuiSplitButtonDropdown, {\n variants: {\n fullWidth: {\n true: zuiSplitButtonFullWidth,\n },\n },\n});\nexport const splitButtonGroupVariants = cva(zuiSplitButtonGroup, {\n variants: {\n fullWidth: {\n true: zuiSplitButtonFullWidth,\n },\n },\n});\nexport const splitButtonPrimaryVariants = cva(zuiSplitButtonPrimary);\nexport const splitButtonTriggerVariants = cva(zuiSplitButtonTrigger, {\n variants: {\n size: zuiSplitButtonTriggerSizes,\n },\n defaultVariants: {\n size: \"md\",\n },\n});\nexport const splitButtonContentVariants = cva(zuiSplitButtonContent);\nexport const splitButtonItemDisabledVariants = cva(zuiSplitButtonItemDisabled);\n","\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { FiChevronDown, FiLoader } from \"react-icons/fi\";\n\nimport {\n Dropdown,\n DropdownContent,\n DropdownItem,\n DropdownTrigger,\n} from \"../dropdown\";\nimport { buttonVariants } from \"../buttons\";\nimport { cn } from \"../../lib/utils\";\n\nimport type {\n SplitButtonAppearance,\n SplitButtonProps,\n SplitButtonVariant,\n} from \"./types\";\nimport {\n splitButtonContentVariants,\n splitButtonDropdownVariants,\n splitButtonGroupVariants,\n splitButtonItemDisabledVariants,\n splitButtonPrimaryVariants,\n splitButtonRootVariants,\n splitButtonTriggerVariants,\n} from \"./variants\";\n\nconst variantAppearanceMap = {\n primary: \"default\",\n secondary: \"secondary\",\n outline: \"outline\",\n ghost: \"ghost\",\n danger: \"destructive\",\n success: \"green\",\n} as const satisfies Record<SplitButtonVariant, SplitButtonAppearance>;\n\nfunction resolveAppearance({\n appearance,\n variant,\n}: {\n appearance?: SplitButtonAppearance;\n variant?: SplitButtonVariant;\n}) {\n return appearance ?? (variant ? variantAppearanceMap[variant] : \"default\");\n}\n\nexport function SplitButtonBase({\n label,\n onClick,\n items,\n disabled = false,\n loading = false,\n appearance,\n variant,\n size = \"md\",\n startIcon,\n fullWidth = false,\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n triggerLabel,\n triggerOn = \"click\",\n className,\n ref,\n ...rest\n}: SplitButtonProps) {\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n const isUnavailable = disabled || loading;\n const resolvedAppearance = resolveAppearance({ appearance, variant });\n const isControlled = controlledOpen !== undefined;\n const open = isUnavailable\n ? false\n : isControlled\n ? controlledOpen\n : uncontrolledOpen;\n\n const setOpen = (nextOpen: boolean) => {\n if (isUnavailable && nextOpen) {\n return;\n }\n if (!isControlled) {\n setUncontrolledOpen(nextOpen);\n }\n onOpenChange?.(nextOpen);\n };\n\n const fullWidthFlag = fullWidth ? \"true\" : undefined;\n const menuLabel = triggerLabel ?? `More ${label} actions`;\n\n // Shared timeout ref for hover mode: delays close so the cursor can\n // travel through the mt-2 gap between the button and the menu panel\n // without dismissing the menu.\n const hoverCloseRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const scheduleClose = () => {\n cancelClose();\n hoverCloseRef.current = setTimeout(() => setOpen(false), 120);\n };\n\n const cancelClose = () => {\n if (hoverCloseRef.current !== null) {\n clearTimeout(hoverCloseRef.current);\n hoverCloseRef.current = null;\n }\n };\n\n useEffect(() => cancelClose, []);\n\n useEffect(() => {\n if (isUnavailable && !isControlled) {\n setUncontrolledOpen(false);\n onOpenChange?.(false);\n }\n }, [isUnavailable, onOpenChange, isControlled]);\n\n const dropdownHoverHandlers =\n triggerOn === \"hover\"\n ? {\n onMouseEnter: () => {\n cancelClose();\n setOpen(true);\n },\n onMouseLeave: scheduleClose,\n }\n : undefined;\n\n const contentHoverHandlers =\n triggerOn === \"hover\"\n ? { onMouseEnter: cancelClose, onMouseLeave: scheduleClose }\n : undefined;\n\n return (\n <div\n ref={ref}\n data-slot=\"split-button\"\n data-full-width={fullWidthFlag}\n className={cn(splitButtonRootVariants({ fullWidth }), className)}\n {...rest}\n >\n <Dropdown\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={setOpen}\n data-full-width={fullWidthFlag}\n className={splitButtonDropdownVariants({ fullWidth })}\n {...dropdownHoverHandlers}\n >\n <div\n data-slot=\"split-button-group\"\n data-full-width={fullWidthFlag}\n className={splitButtonGroupVariants({ fullWidth })}\n >\n <button\n type=\"button\"\n data-slot=\"split-button-primary\"\n data-full-width={fullWidthFlag}\n disabled={isUnavailable}\n onClick={onClick}\n className={cn(\n buttonVariants({ appearance: resolvedAppearance, size }),\n splitButtonPrimaryVariants(),\n )}\n >\n {loading ? (\n <FiLoader\n aria-hidden\n className=\"animate-spin\"\n data-slot=\"split-button-spinner-icon\"\n />\n ) : (\n startIcon\n )}\n <span>{label}</span>\n {loading ? (\n <span className=\"sr-only\" role=\"status\" aria-label=\"Loading\" />\n ) : null}\n </button>\n\n <DropdownTrigger\n aria-label={menuLabel}\n disabled={isUnavailable}\n className={cn(\n buttonVariants({ appearance: resolvedAppearance, size }),\n splitButtonTriggerVariants({ size }),\n )}\n >\n <FiChevronDown aria-hidden />\n </DropdownTrigger>\n </div>\n\n <DropdownContent\n className={splitButtonContentVariants()}\n placement=\"bottom\"\n {...contentHoverHandlers}\n >\n {items.map((item) => (\n <DropdownItem\n key={item.id}\n leftIcon={item.icon}\n aria-disabled={item.disabled ? \"true\" : undefined}\n className={\n item.disabled ? splitButtonItemDisabledVariants() : undefined\n }\n onClick={(event) => {\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }}\n onKeyDown={(event) => {\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }}\n onSelect={() => {\n setOpen(false);\n item.onSelect?.();\n }}\n >\n {item.label}\n </DropdownItem>\n ))}\n </DropdownContent>\n </Dropdown>\n </div>\n );\n}\n\nSplitButtonBase.displayName = \"SplitButton\";\n","// split-button.tsx — default static entry (no framer-motion)\nimport { SplitButtonBase } from \"./split-button-base\";\nimport type { SplitButtonProps } from \"./types\";\n\nexport const SplitButton = (props: SplitButtonProps) => {\n return <SplitButtonBase {...props} />;\n};\n\nSplitButton.displayName = \"SplitButton\";\n"]}
1
+ {"version":3,"sources":["../../src/ui/split-button/variants.ts","../../src/ui/split-button/split-button-base.tsx","../../src/ui/split-button/split-button.tsx"],"names":["jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcO,IAAM,uBAAA,GAA0B,IAAI,kBAAA,EAAoB;AAAA,EAC7D,QAAA,EAAU;AAAA,IACR,SAAA,EAAW;AAAA,MACT,IAAA,EAAM;AAAA;AACR;AAEJ,CAAC;AACM,IAAM,2BAAA,GAA8B,IAAI,sBAAA,EAAwB;AAAA,EACrE,QAAA,EAAU;AAAA,IACR,SAAA,EAAW;AAAA,MACT,IAAA,EAAM;AAAA;AACR;AAEJ,CAAC;AACM,IAAM,wBAAA,GAA2B,IAAI,mBAAA,EAAqB;AAAA,EAC/D,QAAA,EAAU;AAAA,IACR,SAAA,EAAW;AAAA,MACT,IAAA,EAAM;AAAA;AACR;AAEJ,CAAC;AACM,IAAM,0BAAA,GAA6B,IAAI,qBAAqB;AAC5D,IAAM,0BAAA,GAA6B,IAAI,qBAAA,EAAuB;AAAA,EACnE,QAAA,EAAU;AAAA,IACR,IAAA,EAAM;AAAA,GACR;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,IAAA,EAAM;AAAA;AAEV,CAAC;AACM,IAAM,0BAAA,GAA6B,IAAI,qBAAqB;AAC5D,IAAM,+BAAA,GAAkC,IAAI,0BAA0B;AChB7E,IAAM,oBAAA,GAAuB;AAAA,EAC3B,OAAA,EAAS,SAAA;AAAA,EACT,SAAA,EAAW,WAAA;AAAA,EACX,OAAA,EAAS,SAAA;AAAA,EACT,KAAA,EAAO,OAAA;AAAA,EACP,MAAA,EAAQ,aAAA;AAAA,EACR,OAAA,EAAS;AACX,CAAA;AAEA,SAAS,iBAAA,CAAkB;AAAA,EACzB,UAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,OAAO,UAAA,KAAe,OAAA,GAAU,oBAAA,CAAqB,OAAO,CAAA,GAAI,SAAA,CAAA;AAClE;AAEO,SAAS,eAAA,CAAgB;AAAA,EAC9B,KAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,OAAA,GAAU,KAAA;AAAA,EACV,UAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA,GAAO,IAAA;AAAA,EACP,SAAA;AAAA,EACA,SAAA,GAAY,KAAA;AAAA,EACZ,IAAA,EAAM,cAAA;AAAA,EACN,WAAA,GAAc,KAAA;AAAA,EACd,YAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA,GAAY,OAAA;AAAA,EACZ,SAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAqB;AACnB,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAI,SAAS,WAAW,CAAA;AACpE,EAAA,MAAM,gBAAgB,QAAA,IAAY,OAAA;AAClC,EAAA,MAAM,kBAAA,GAAqB,iBAAA,CAAkB,EAAE,UAAA,EAAY,SAAS,CAAA;AACpE,EAAA,MAAM,eAAe,cAAA,KAAmB,MAAA;AACxC,EAAA,MAAM,IAAA,GAAO,aAAA,GACT,KAAA,GACA,YAAA,GACE,cAAA,GACA,gBAAA;AAEN,EAAA,MAAM,OAAA,GAAU,CAAC,QAAA,KAAsB;AACrC,IAAA,IAAI,iBAAiB,QAAA,EAAU;AAC7B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA,mBAAA,CAAoB,QAAQ,CAAA;AAAA,IAC9B;AACA,IAAA,YAAA,GAAe,QAAQ,CAAA;AAAA,EACzB,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB,YAAY,MAAA,GAAS,MAAA;AAC3C,EAAA,MAAM,SAAA,GAAY,YAAA,IAAgB,CAAA,KAAA,EAAQ,KAAK,CAAA,QAAA,CAAA;AAK/C,EAAA,MAAM,aAAA,GAAgB,OAA6C,IAAI,CAAA;AAEvE,EAAA,MAAM,gBAAgB,MAAM;AAC1B,IAAA,WAAA,EAAY;AACZ,IAAA,aAAA,CAAc,UAAU,UAAA,CAAW,MAAM,OAAA,CAAQ,KAAK,GAAG,GAAG,CAAA;AAAA,EAC9D,CAAA;AAEA,EAAA,MAAM,cAAc,MAAM;AACxB,IAAA,IAAI,aAAA,CAAc,YAAY,IAAA,EAAM;AAClC,MAAA,YAAA,CAAa,cAAc,OAAO,CAAA;AAClC,MAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AAAA,IAC1B;AAAA,EACF,CAAA;AAEA,EAAA,SAAA,CAAU,MAAM,WAAA,EAAa,EAAE,CAAA;AAE/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,aAAA,IAAiB,CAAC,YAAA,EAAc;AAClC,MAAA,mBAAA,CAAoB,KAAK,CAAA;AACzB,MAAA,YAAA,GAAe,KAAK,CAAA;AAAA,IACtB;AAAA,EACF,CAAA,EAAG,CAAC,aAAA,EAAe,YAAA,EAAc,YAAY,CAAC,CAAA;AAE9C,EAAA,MAAM,qBAAA,GACJ,cAAc,OAAA,GACV;AAAA,IACE,cAAc,MAAM;AAClB,MAAA,WAAA,EAAY;AACZ,MAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,IACd,CAAA;AAAA,IACA,YAAA,EAAc;AAAA,GAChB,GACA,MAAA;AAEN,EAAA,MAAM,oBAAA,GACJ,cAAc,OAAA,GACV,EAAE,cAAc,WAAA,EAAa,YAAA,EAAc,eAAc,GACzD,MAAA;AAEN,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,cAAA;AAAA,MACV,iBAAA,EAAiB,aAAA;AAAA,MACjB,WAAW,EAAA,CAAG,uBAAA,CAAwB,EAAE,SAAA,EAAW,GAAG,SAAS,CAAA;AAAA,MAC9D,GAAG,IAAA;AAAA,MAEJ,QAAA,kBAAA,IAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA;AAAA,UACA,WAAA;AAAA,UACA,YAAA,EAAc,OAAA;AAAA,UACd,iBAAA,EAAiB,aAAA;AAAA,UACjB,SAAA,EAAW,2BAAA,CAA4B,EAAE,SAAA,EAAW,CAAA;AAAA,UACnD,GAAG,qBAAA;AAAA,UAEJ,QAAA,EAAA;AAAA,4BAAA,IAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,WAAA,EAAU,oBAAA;AAAA,gBACV,iBAAA,EAAiB,aAAA;AAAA,gBACjB,SAAA,EAAW,wBAAA,CAAyB,EAAE,SAAA,EAAW,CAAA;AAAA,gBAEjD,QAAA,EAAA;AAAA,kCAAA,IAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,IAAA,EAAK,QAAA;AAAA,sBACL,WAAA,EAAU,sBAAA;AAAA,sBACV,iBAAA,EAAiB,aAAA;AAAA,sBACjB,QAAA,EAAU,aAAA;AAAA,sBACV,OAAA;AAAA,sBACA,SAAA,EAAW,EAAA;AAAA,wBACT,cAAA,CAAe,EAAE,UAAA,EAAY,kBAAA,EAAoB,MAAM,CAAA;AAAA,wBACvD,0BAAA;AAA2B,uBAC7B;AAAA,sBAEC,QAAA,EAAA;AAAA,wBAAA,OAAA,mBACC,GAAA;AAAA,0BAAC,QAAA;AAAA,0BAAA;AAAA,4BACC,aAAA,EAAW,IAAA;AAAA,4BACX,SAAA,EAAU,cAAA;AAAA,4BACV,WAAA,EAAU;AAAA;AAAA,yBACZ,GAEA,SAAA;AAAA,wCAEF,GAAA,CAAC,UAAM,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,wBACZ,OAAA,uBACE,MAAA,EAAA,EAAK,SAAA,EAAU,WAAU,IAAA,EAAK,QAAA,EAAS,YAAA,EAAW,SAAA,EAAU,CAAA,GAC3D;AAAA;AAAA;AAAA,mBACN;AAAA,kCAEA,GAAA;AAAA,oBAAC,eAAA;AAAA,oBAAA;AAAA,sBACC,YAAA,EAAY,SAAA;AAAA,sBACZ,QAAA,EAAU,aAAA;AAAA,sBACV,SAAA,EAAW,EAAA;AAAA,wBACT,cAAA,CAAe,EAAE,UAAA,EAAY,kBAAA,EAAoB,MAAM,CAAA;AAAA,wBACvD,0BAAA,CAA2B,EAAE,IAAA,EAAM;AAAA,uBACrC;AAAA,sBAEA,QAAA,kBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,aAAA,EAAW,IAAA,EAAC;AAAA;AAAA;AAC7B;AAAA;AAAA,aACF;AAAA,4BAEA,GAAA;AAAA,cAAC,eAAA;AAAA,cAAA;AAAA,gBACC,WAAW,0BAAA,EAA2B;AAAA,gBACtC,SAAA,EAAU,QAAA;AAAA,gBACT,GAAG,oBAAA;AAAA,gBAEH,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,qBACV,GAAA;AAAA,kBAAC,YAAA;AAAA,kBAAA;AAAA,oBAEC,UAAU,IAAA,CAAK,IAAA;AAAA,oBACf,eAAA,EAAe,IAAA,CAAK,QAAA,GAAW,MAAA,GAAS,MAAA;AAAA,oBACxC,SAAA,EACE,IAAA,CAAK,QAAA,GAAW,+BAAA,EAAgC,GAAI,MAAA;AAAA,oBAEtD,OAAA,EAAS,CAAC,KAAA,KAAU;AAClB,sBAAA,IAAI,KAAK,QAAA,EAAU;AACjB,wBAAA,KAAA,CAAM,cAAA,EAAe;AACrB,wBAAA,KAAA,CAAM,eAAA,EAAgB;AAAA,sBACxB;AAAA,oBACF,CAAA;AAAA,oBACA,SAAA,EAAW,CAAC,KAAA,KAAU;AACpB,sBAAA,IAAI,KAAK,QAAA,EAAU;AACjB,wBAAA,KAAA,CAAM,cAAA,EAAe;AACrB,wBAAA,KAAA,CAAM,eAAA,EAAgB;AAAA,sBACxB;AAAA,oBACF,CAAA;AAAA,oBACA,UAAU,MAAM;AACd,sBAAA,OAAA,CAAQ,KAAK,CAAA;AACb,sBAAA,IAAA,CAAK,QAAA,IAAW;AAAA,oBAClB,CAAA;AAAA,oBAEC,QAAA,EAAA,IAAA,CAAK;AAAA,mBAAA;AAAA,kBAvBD,IAAA,CAAK;AAAA,iBAyBb;AAAA;AAAA;AACH;AAAA;AAAA;AACF;AAAA,GACF;AAEJ;AAEA,eAAA,CAAgB,WAAA,GAAc,aAAA;ACnOvB,IAAM,WAAA,GAAc,CAAC,KAAA,KAA4B;AACtD,EAAA,uBAAOA,GAAAA,CAAC,eAAA,EAAA,EAAiB,GAAG,KAAA,EAAO,CAAA;AACrC;AAEA,WAAA,CAAY,WAAA,GAAc,aAAA","file":"split-button.mjs","sourcesContent":["import { cva } from \"class-variance-authority\";\n\nimport {\n zuiSplitButtonContent,\n zuiSplitButtonDropdown,\n zuiSplitButtonFullWidth,\n zuiSplitButtonGroup,\n zuiSplitButtonItemDisabled,\n zuiSplitButtonPrimary,\n zuiSplitButtonRoot,\n zuiSplitButtonTrigger,\n zuiSplitButtonTriggerSizes,\n} from \"../../design-system\";\n\nexport const splitButtonRootVariants = cva(zuiSplitButtonRoot, {\n variants: {\n fullWidth: {\n true: zuiSplitButtonFullWidth,\n },\n },\n});\nexport const splitButtonDropdownVariants = cva(zuiSplitButtonDropdown, {\n variants: {\n fullWidth: {\n true: zuiSplitButtonFullWidth,\n },\n },\n});\nexport const splitButtonGroupVariants = cva(zuiSplitButtonGroup, {\n variants: {\n fullWidth: {\n true: zuiSplitButtonFullWidth,\n },\n },\n});\nexport const splitButtonPrimaryVariants = cva(zuiSplitButtonPrimary);\nexport const splitButtonTriggerVariants = cva(zuiSplitButtonTrigger, {\n variants: {\n size: zuiSplitButtonTriggerSizes,\n },\n defaultVariants: {\n size: \"md\",\n },\n});\nexport const splitButtonContentVariants = cva(zuiSplitButtonContent);\nexport const splitButtonItemDisabledVariants = cva(zuiSplitButtonItemDisabled);\n","\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { FiChevronDown, FiLoader } from \"react-icons/fi\";\n\nimport {\n Dropdown,\n DropdownContent,\n DropdownItem,\n DropdownTrigger,\n} from \"../dropdown\";\nimport { buttonVariants } from \"../buttons\";\nimport { cn } from \"../../lib/utils\";\n\nimport type {\n SplitButtonAppearance,\n SplitButtonProps,\n SplitButtonVariant,\n} from \"./types\";\nimport {\n splitButtonContentVariants,\n splitButtonDropdownVariants,\n splitButtonGroupVariants,\n splitButtonItemDisabledVariants,\n splitButtonPrimaryVariants,\n splitButtonRootVariants,\n splitButtonTriggerVariants,\n} from \"./variants\";\n\nconst variantAppearanceMap = {\n primary: \"default\",\n secondary: \"secondary\",\n outline: \"outline\",\n ghost: \"ghost\",\n danger: \"destructive\",\n success: \"green\",\n} as const satisfies Record<SplitButtonVariant, SplitButtonAppearance>;\n\nfunction resolveAppearance({\n appearance,\n variant,\n}: {\n appearance?: SplitButtonAppearance;\n variant?: SplitButtonVariant;\n}) {\n return appearance ?? (variant ? variantAppearanceMap[variant] : \"default\");\n}\n\nexport function SplitButtonBase({\n label,\n onClick,\n items,\n disabled = false,\n loading = false,\n appearance,\n variant,\n size = \"md\",\n startIcon,\n fullWidth = false,\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n triggerLabel,\n triggerOn = \"click\",\n className,\n ref,\n ...rest\n}: SplitButtonProps) {\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n const isUnavailable = disabled || loading;\n const resolvedAppearance = resolveAppearance({ appearance, variant });\n const isControlled = controlledOpen !== undefined;\n const open = isUnavailable\n ? false\n : isControlled\n ? controlledOpen\n : uncontrolledOpen;\n\n const setOpen = (nextOpen: boolean) => {\n if (isUnavailable && nextOpen) {\n return;\n }\n if (!isControlled) {\n setUncontrolledOpen(nextOpen);\n }\n onOpenChange?.(nextOpen);\n };\n\n const fullWidthFlag = fullWidth ? \"true\" : undefined;\n const menuLabel = triggerLabel ?? `More ${label} actions`;\n\n // Shared timeout ref for hover mode: delays close so the cursor can\n // travel through the mt-2 gap between the button and the menu panel\n // without dismissing the menu.\n const hoverCloseRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const scheduleClose = () => {\n cancelClose();\n hoverCloseRef.current = setTimeout(() => setOpen(false), 120);\n };\n\n const cancelClose = () => {\n if (hoverCloseRef.current !== null) {\n clearTimeout(hoverCloseRef.current);\n hoverCloseRef.current = null;\n }\n };\n\n useEffect(() => cancelClose, []);\n\n useEffect(() => {\n if (isUnavailable && !isControlled) {\n setUncontrolledOpen(false);\n onOpenChange?.(false);\n }\n }, [isUnavailable, onOpenChange, isControlled]);\n\n const dropdownHoverHandlers =\n triggerOn === \"hover\"\n ? {\n onMouseEnter: () => {\n cancelClose();\n setOpen(true);\n },\n onMouseLeave: scheduleClose,\n }\n : undefined;\n\n const contentHoverHandlers =\n triggerOn === \"hover\"\n ? { onMouseEnter: cancelClose, onMouseLeave: scheduleClose }\n : undefined;\n\n return (\n <div\n ref={ref}\n data-slot=\"split-button\"\n data-full-width={fullWidthFlag}\n className={cn(splitButtonRootVariants({ fullWidth }), className)}\n {...rest}\n >\n <Dropdown\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={setOpen}\n data-full-width={fullWidthFlag}\n className={splitButtonDropdownVariants({ fullWidth })}\n {...dropdownHoverHandlers}\n >\n <div\n data-slot=\"split-button-group\"\n data-full-width={fullWidthFlag}\n className={splitButtonGroupVariants({ fullWidth })}\n >\n <button\n type=\"button\"\n data-slot=\"split-button-primary\"\n data-full-width={fullWidthFlag}\n disabled={isUnavailable}\n onClick={onClick}\n className={cn(\n buttonVariants({ appearance: resolvedAppearance, size }),\n splitButtonPrimaryVariants(),\n )}\n >\n {loading ? (\n <FiLoader\n aria-hidden\n className=\"animate-spin\"\n data-slot=\"split-button-spinner-icon\"\n />\n ) : (\n startIcon\n )}\n <span>{label}</span>\n {loading ? (\n <span className=\"sr-only\" role=\"status\" aria-label=\"Loading\" />\n ) : null}\n </button>\n\n <DropdownTrigger\n aria-label={menuLabel}\n disabled={isUnavailable}\n className={cn(\n buttonVariants({ appearance: resolvedAppearance, size }),\n splitButtonTriggerVariants({ size }),\n )}\n >\n <FiChevronDown aria-hidden />\n </DropdownTrigger>\n </div>\n\n <DropdownContent\n className={splitButtonContentVariants()}\n placement=\"bottom\"\n {...contentHoverHandlers}\n >\n {items.map((item) => (\n <DropdownItem\n key={item.id}\n leftIcon={item.icon}\n aria-disabled={item.disabled ? \"true\" : undefined}\n className={\n item.disabled ? splitButtonItemDisabledVariants() : undefined\n }\n onClick={(event) => {\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }}\n onKeyDown={(event) => {\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n }\n }}\n onSelect={() => {\n setOpen(false);\n item.onSelect?.();\n }}\n >\n {item.label}\n </DropdownItem>\n ))}\n </DropdownContent>\n </Dropdown>\n </div>\n );\n}\n\nSplitButtonBase.displayName = \"SplitButton\";\n","// split-button.tsx — default static entry (no framer-motion)\nimport { SplitButtonBase } from \"./split-button-base\";\nimport type { SplitButtonProps } from \"./types\";\n\nexport const SplitButton = (props: SplitButtonProps) => {\n return <SplitButtonBase {...props} />;\n};\n\nSplitButton.displayName = \"SplitButton\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zentauri-ui/zentauri-components",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "React + Tailwind UI kit with charts, ESM/CJS builds, per-entry exports, and a zentauri-components / zentauri-ui CLI to vendor UI or hook source into your app",
5
5
  "keywords": [
6
6
  "react",
@@ -0,0 +1,34 @@
1
+ export const zuiHashGeneratorBase = [
2
+ "rounded-lg border border-[color:var(--zui-hash-generator-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-hash-generator-border-dark,var(--zui-border-dark,#ffffff1a))]",
3
+ "bg-[var(--zui-hash-generator-bg,var(--zui-surface,oklch(98.4%_0.003_247.858)))] dark:bg-[var(--zui-hash-generator-bg-dark,var(--zui-surface-dark,oklch(12.9%_0.042_264.695)))]",
4
+ "text-[color:var(--zui-hash-generator-fg,var(--zui-fg,oklch(20.8%_0.042_265.755)))] dark:text-[color:var(--zui-hash-generator-fg-dark,var(--zui-fg-dark,oklch(98.4%_0.003_247.858)))]",
5
+ ] as const;
6
+
7
+ export const zuiHashGeneratorHeaderBase =
8
+ "flex items-center justify-between border-b border-[color:var(--zui-hash-generator-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-hash-generator-border-dark,var(--zui-border-dark,#ffffff1a))] bg-[var(--zui-hash-generator-header-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:bg-[var(--zui-hash-generator-header-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))] px-4 py-2";
9
+
10
+ export const zuiHashGeneratorLabelBase =
11
+ "text-xs font-medium text-[color:var(--zui-hash-generator-label-fg,var(--zui-fg-muted,oklch(55.2%_0.046_257.417)))] dark:text-[color:var(--zui-hash-generator-label-fg-dark,var(--zui-fg-muted-dark,oklch(70.8%_0.015_256.243)))]";
12
+
13
+ export const zuiHashGeneratorInputBase = [
14
+ "w-full bg-transparent px-4 py-3 text-sm outline-none placeholder:text-[color:var(--zui-hash-generator-placeholder,var(--zui-fg-muted,oklch(55.2%_0.046_257.417)))] dark:placeholder:text-[color:var(--zui-hash-generator-placeholder-dark,var(--zui-fg-muted-dark,oklch(70.8%_0.015_256.243)))]",
15
+ "focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--zui-hash-generator-ring-focus,var(--zui-focus-ring,#0000004d))] dark:focus-visible:ring-[var(--zui-hash-generator-ring-focus-dark,var(--zui-focus-ring-dark,#ffffff4d))]",
16
+ ] as const;
17
+
18
+ export const zuiHashGeneratorOutputBase = [
19
+ "relative border-t border-[color:var(--zui-hash-generator-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-hash-generator-border-dark,var(--zui-border-dark,#ffffff1a))]",
20
+ "bg-[var(--zui-hash-generator-output-bg,oklch(96.8%_0.007_247.896))] dark:bg-[var(--zui-hash-generator-output-bg-dark,oklch(18.5%_0.037_264.653))]",
21
+ ] as const;
22
+
23
+ export const zuiHashGeneratorOutputTextBase =
24
+ "font-mono text-xs break-all px-4 py-3 text-[color:var(--zui-hash-generator-output-fg,var(--zui-brand-fg,oklch(44.6%_0.043_257.281)))] dark:text-[color:var(--zui-hash-generator-output-fg-dark,var(--zui-brand-fg-dark,oklch(86.9%_0.022_252.894)))]";
25
+
26
+ export const zuiHashGeneratorSizes = {
27
+ sm: "",
28
+ md: "",
29
+ lg: "",
30
+ } as const;
31
+
32
+ export const zuiHashGeneratorAppearances = {
33
+ default: "",
34
+ } as const;
@@ -8,6 +8,7 @@ export * from "./breadcrumb";
8
8
  export * from "./button";
9
9
  export * from "./card";
10
10
  export * from "./code-diff";
11
+ export * from "./hash-generator";
11
12
  export * from "./checkbox";
12
13
  export * from "./combobox";
13
14
  export * from "./command";
@@ -53,6 +53,12 @@ export {
53
53
  type UseDocumentTitleParams,
54
54
  } from "./useDocumentTitle";
55
55
  export { useHover } from "./useHover";
56
+ export {
57
+ useHash,
58
+ type HashGeneratorAlgorithm,
59
+ type UseHashResult,
60
+ ALGORITHM_LABELS,
61
+ } from "./useHash";
56
62
  export {
57
63
  useIdleTimeout,
58
64
  type UseIdleTimeoutParams,
@@ -0,0 +1,6 @@
1
+ export {
2
+ useHash,
3
+ type HashGeneratorAlgorithm,
4
+ type UseHashResult,
5
+ ALGORITHM_LABELS,
6
+ } from "./useHash";
@@ -0,0 +1,77 @@
1
+ import { act, renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ import { useHash } from "./useHash";
5
+
6
+ vi.stubGlobal("crypto", {
7
+ subtle: {
8
+ digest: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4]).buffer),
9
+ },
10
+ });
11
+
12
+ describe("useHash", () => {
13
+ it("should return empty hash for empty input", () => {
14
+ const { result } = renderHook(() => useHash(""));
15
+ expect(result.current.hash).toBe("");
16
+ expect(result.current.isHashing).toBe(false);
17
+ expect(result.current.error).toBeUndefined();
18
+ });
19
+
20
+ it("should compute hash for non-empty input", async () => {
21
+ const { result } = renderHook(() => useHash("hello"));
22
+ await vi.waitFor(() => {
23
+ expect(result.current.hash).toBe("01020304");
24
+ });
25
+ expect(result.current.isHashing).toBe(false);
26
+ expect(result.current.error).toBeUndefined();
27
+ });
28
+
29
+ it("should recompute when algorithm changes", async () => {
30
+ const { result, rerender } = renderHook(
31
+ ({ input, algo }: { input: string; algo: "sha256" | "sha512" }) =>
32
+ useHash(input, algo),
33
+ { initialProps: { input: "hello", algo: "sha256" } },
34
+ );
35
+ await vi.waitFor(() => {
36
+ expect(result.current.hash).toBe("01020304");
37
+ });
38
+ rerender({ input: "hello", algo: "sha512" });
39
+ expect(result.current.isHashing).toBe(true);
40
+ });
41
+
42
+ it("should recompute when input changes", async () => {
43
+ const { result, rerender } = renderHook(
44
+ ({ input }: { input: string }) => useHash(input),
45
+ { initialProps: { input: "hello" } },
46
+ );
47
+ await vi.waitFor(() => {
48
+ expect(result.current.hash).toBe("01020304");
49
+ });
50
+ rerender({ input: "world" });
51
+ expect(result.current.isHashing).toBe(true);
52
+ });
53
+
54
+ it("should call recompute on demand", async () => {
55
+ const { result } = renderHook(() => useHash("hello"));
56
+ await vi.waitFor(() => {
57
+ expect(result.current.hash).toBe("01020304");
58
+ });
59
+ act(() => {
60
+ result.current.recompute();
61
+ });
62
+ expect(result.current.isHashing).toBe(true);
63
+ });
64
+
65
+ it("should clear hash when input becomes empty", async () => {
66
+ const { result, rerender } = renderHook(
67
+ ({ input }: { input: string }) => useHash(input),
68
+ { initialProps: { input: "hello" } },
69
+ );
70
+ await vi.waitFor(() => {
71
+ expect(result.current.hash).toBe("01020304");
72
+ });
73
+ rerender({ input: "" });
74
+ expect(result.current.hash).toBe("");
75
+ expect(result.current.isHashing).toBe(false);
76
+ });
77
+ });
@@ -0,0 +1,89 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+
5
+ export type HashGeneratorAlgorithm = "sha1" | "sha256" | "sha384" | "sha512";
6
+
7
+ export const ALGORITHM_LABELS: Record<HashGeneratorAlgorithm, string> = {
8
+ sha1: "SHA-1",
9
+ sha256: "SHA-256",
10
+ sha384: "SHA-384",
11
+ sha512: "SHA-512",
12
+ };
13
+
14
+ async function computeHash(
15
+ algorithm: HashGeneratorAlgorithm,
16
+ input: string,
17
+ ): Promise<string> {
18
+ if (typeof crypto === "undefined" || !crypto.subtle) {
19
+ throw new Error("Web Crypto API is not supported in this environment.");
20
+ }
21
+ const encoder = new TextEncoder();
22
+ const data = encoder.encode(input);
23
+ const hashBuffer = await crypto.subtle.digest(
24
+ algorithm.toUpperCase().replace("SHA", "SHA-") as AlgorithmIdentifier,
25
+ data,
26
+ );
27
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
28
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
29
+ }
30
+
31
+ export type UseHashResult = {
32
+ hash: string;
33
+ isHashing: boolean;
34
+ error: Error | undefined;
35
+ recompute: () => void;
36
+ };
37
+
38
+ /**
39
+ * Computes a cryptographic hash of `input` using the Web Crypto API.
40
+ *
41
+ * Automatically recomputes whenever `input` or `algorithm` changes.
42
+ * Returns `{ hash, isHashing, error, recompute }` for rendering.
43
+ *
44
+ * @param input - The string to hash.
45
+ * @param algorithm - Hash algorithm (default `"sha256"`).
46
+ * @returns `{ hash, isHashing, error, recompute }`
47
+ */
48
+ export function useHash(
49
+ input: string,
50
+ algorithm: HashGeneratorAlgorithm = "sha256",
51
+ ): UseHashResult {
52
+ const [hash, setHash] = useState("");
53
+ const [isHashing, setIsHashing] = useState(false);
54
+ const [error, setError] = useState<Error | undefined>(undefined);
55
+ const [trigger, setTrigger] = useState(0);
56
+ const nonceRef = useRef(0);
57
+
58
+ useEffect(() => {
59
+ if (!input) {
60
+ setHash("");
61
+ setIsHashing(false);
62
+ setError(undefined);
63
+ return;
64
+ }
65
+ const nonce = ++nonceRef.current;
66
+ setIsHashing(true);
67
+ setError(undefined);
68
+ computeHash(algorithm, input).then(
69
+ (result) => {
70
+ if (nonce === nonceRef.current) {
71
+ setHash(result);
72
+ setIsHashing(false);
73
+ }
74
+ },
75
+ (err) => {
76
+ if (nonce === nonceRef.current) {
77
+ setError(err instanceof Error ? err : new Error(String(err)));
78
+ setIsHashing(false);
79
+ }
80
+ },
81
+ );
82
+ }, [algorithm, input, trigger]);
83
+
84
+ const recompute = useCallback(() => {
85
+ setTrigger((prev) => prev + 1);
86
+ }, []);
87
+
88
+ return { hash, isHashing, error, recompute };
89
+ }
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useState } from "react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+ import { useHash } from "../../hooks/useHash";
7
+
8
+ import type { HashGeneratorBaseProps } from "./types";
9
+ import { ALGORITHM_LABELS } from "./types";
10
+ import {
11
+ hashGeneratorHeaderVariants,
12
+ hashGeneratorInputVariants,
13
+ hashGeneratorLabelVariants,
14
+ hashGeneratorOutputTextVariants,
15
+ hashGeneratorOutputVariants,
16
+ hashGeneratorVariants,
17
+ } from "./variants";
18
+
19
+ export function HashGeneratorBase({
20
+ className,
21
+ appearance,
22
+ size,
23
+ algorithm = "sha256",
24
+ value,
25
+ onValueChange,
26
+ readOnly = false,
27
+ showCopyButton = true,
28
+ ref,
29
+ ...rest
30
+ }: HashGeneratorBaseProps) {
31
+ const [internalValue, setInternalValue] = useState("");
32
+ const [copied, setCopied] = useState(false);
33
+
34
+ const inputValue = value ?? internalValue;
35
+ const handleChange = onValueChange ?? setInternalValue;
36
+
37
+ const { hash, error } = useHash(inputValue, algorithm);
38
+
39
+ useEffect(() => {
40
+ if (!copied) return;
41
+ const timeout = setTimeout(() => setCopied(false), 2000);
42
+ return () => clearTimeout(timeout);
43
+ }, [copied]);
44
+
45
+ const handleCopy = useCallback(async () => {
46
+ if (!hash) return;
47
+ try {
48
+ await navigator.clipboard.writeText(hash);
49
+ setCopied(true);
50
+ } catch {
51
+ // Clipboard API not available
52
+ }
53
+ }, [hash]);
54
+
55
+ return (
56
+ <div
57
+ ref={ref}
58
+ data-slot="hash-generator"
59
+ className={cn(hashGeneratorVariants({ appearance, size }), className)}
60
+ {...rest}
61
+ >
62
+ <div className={hashGeneratorHeaderVariants()}>
63
+ <span className={hashGeneratorLabelVariants()}>
64
+ {ALGORITHM_LABELS[algorithm]}
65
+ </span>
66
+ {showCopyButton && hash ? (
67
+ <button
68
+ type="button"
69
+ onClick={handleCopy}
70
+ className="rounded px-2 py-0.5 text-xs font-medium transition-colors text-[color:var(--zui-hash-generator-label-fg,var(--zui-fg-muted,oklch(55.2%_0.046_257.417)))] dark:text-[color:var(--zui-hash-generator-label-fg-dark,var(--zui-fg-muted-dark,oklch(70.8%_0.015_256.243)))] hover:bg-[var(--zui-hash-generator-header-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:hover:bg-[var(--zui-hash-generator-header-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))]"
71
+ >
72
+ {copied ? "Copied!" : "Copy"}
73
+ </button>
74
+ ) : null}
75
+ </div>
76
+ <textarea
77
+ data-slot="hash-generator-input"
78
+ value={inputValue}
79
+ onChange={(e) => handleChange(e.target.value)}
80
+ readOnly={readOnly}
81
+ placeholder="Enter text to hash..."
82
+ rows={3}
83
+ aria-label={`Input text to hash using ${ALGORITHM_LABELS[algorithm]}`}
84
+ className={cn(hashGeneratorInputVariants(), "resize-y min-h-[5rem]")}
85
+ />
86
+ <div className={hashGeneratorOutputVariants()}>
87
+ <span
88
+ role="status"
89
+ data-slot="hash-generator-output"
90
+ className={cn(
91
+ hashGeneratorOutputTextVariants(),
92
+ !hash && !error && "opacity-40",
93
+ )}
94
+ >
95
+ {error ? (
96
+ <span className="text-red-500">{error.message}</span>
97
+ ) : (
98
+ hash || "Hash output"
99
+ )}
100
+ </span>
101
+ </div>
102
+ </div>
103
+ );
104
+ }
105
+
106
+ HashGeneratorBase.displayName = "HashGenerator";
@@ -0,0 +1,73 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { describe, expect, it, vi } from "vitest";
5
+
6
+ import { HashGenerator } from "./hash-generator";
7
+
8
+ // Mock crypto.subtle.digest for jsdom
9
+ vi.stubGlobal("crypto", {
10
+ subtle: {
11
+ digest: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4]).buffer),
12
+ },
13
+ });
14
+
15
+ describe("HashGenerator", () => {
16
+ it("should expose displayName", () => {
17
+ expect(HashGenerator.displayName).toBe("HashGenerator");
18
+ });
19
+
20
+ it("should stamp data-slot", () => {
21
+ render(<HashGenerator />);
22
+ const root = document.querySelector('[data-slot="hash-generator"]');
23
+ expect(root).toBeTruthy();
24
+ expect(root?.getAttribute("data-slot")).toBe("hash-generator");
25
+ });
26
+
27
+ it("should render algorithm label", () => {
28
+ render(<HashGenerator algorithm="sha256" />);
29
+ expect(screen.getByText("SHA-256")).toBeInTheDocument();
30
+ });
31
+
32
+ it("should render with sha512 algorithm", () => {
33
+ render(<HashGenerator algorithm="sha512" />);
34
+ expect(screen.getByText("SHA-512")).toBeInTheDocument();
35
+ });
36
+
37
+ it("should render textarea for input", () => {
38
+ render(<HashGenerator />);
39
+ expect(
40
+ screen.getByPlaceholderText("Enter text to hash..."),
41
+ ).toBeInTheDocument();
42
+ });
43
+
44
+ it("should display hash output", async () => {
45
+ render(<HashGenerator />);
46
+ const textarea = screen.getByPlaceholderText("Enter text to hash...");
47
+ await userEvent.type(textarea, "hello");
48
+ const output = document.querySelector(
49
+ '[data-slot="hash-generator-output"]',
50
+ );
51
+ expect(output).toBeTruthy();
52
+ });
53
+
54
+ it("should forward ref", () => {
55
+ const ref = createRef<HTMLDivElement>();
56
+ render(<HashGenerator ref={ref} />);
57
+ expect(ref.current?.getAttribute("data-slot")).toBe("hash-generator");
58
+ });
59
+
60
+ it("should accept controlled value via value prop", () => {
61
+ render(<HashGenerator value="test text" readOnly />);
62
+ const textarea = screen.getByPlaceholderText("Enter text to hash...");
63
+ expect(textarea).toHaveValue("test text");
64
+ });
65
+
66
+ it("should call onValueChange when typing", async () => {
67
+ const handleChange = vi.fn();
68
+ render(<HashGenerator onValueChange={handleChange} />);
69
+ const textarea = screen.getByPlaceholderText("Enter text to hash...");
70
+ await userEvent.type(textarea, "a");
71
+ expect(handleChange).toHaveBeenCalledWith("a");
72
+ });
73
+ });
@@ -0,0 +1 @@
1
+ export { HashGeneratorBase as HashGenerator } from "./hash-generator-base";
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ export { HashGenerator } from "./hash-generator";
4
+ export type {
5
+ HashGeneratorAlgorithm,
6
+ HashGeneratorBaseProps,
7
+ HashGeneratorProps,
8
+ HashGeneratorVariantProps,
9
+ } from "./types";
10
+ export { ALGORITHM_LABELS } from "./types";
11
+ export {
12
+ hashGeneratorHeaderVariants,
13
+ hashGeneratorInputVariants,
14
+ hashGeneratorLabelVariants,
15
+ hashGeneratorOutputTextVariants,
16
+ hashGeneratorOutputVariants,
17
+ hashGeneratorVariants,
18
+ } from "./variants";
@@ -0,0 +1,29 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { ComponentPropsWithRef } from "react";
3
+
4
+ import type { hashGeneratorVariants } from "./variants";
5
+
6
+ export type HashGeneratorAlgorithm = "sha1" | "sha256" | "sha384" | "sha512";
7
+
8
+ export type HashGeneratorVariantProps = VariantProps<
9
+ typeof hashGeneratorVariants
10
+ >;
11
+
12
+ export interface HashGeneratorBaseProps extends ComponentPropsWithRef<"div"> {
13
+ algorithm?: HashGeneratorAlgorithm;
14
+ value?: string;
15
+ onValueChange?: (value: string) => void;
16
+ readOnly?: boolean;
17
+ showCopyButton?: boolean;
18
+ appearance?: HashGeneratorVariantProps["appearance"];
19
+ size?: HashGeneratorVariantProps["size"];
20
+ }
21
+
22
+ export type HashGeneratorProps = HashGeneratorBaseProps;
23
+
24
+ export const ALGORITHM_LABELS: Record<HashGeneratorAlgorithm, string> = {
25
+ sha1: "SHA-1",
26
+ sha256: "SHA-256",
27
+ sha384: "SHA-384",
28
+ sha512: "SHA-512",
29
+ } as const;
@@ -0,0 +1,31 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ import {
4
+ zuiHashGeneratorAppearances,
5
+ zuiHashGeneratorBase,
6
+ zuiHashGeneratorHeaderBase,
7
+ zuiHashGeneratorInputBase,
8
+ zuiHashGeneratorLabelBase,
9
+ zuiHashGeneratorOutputBase,
10
+ zuiHashGeneratorOutputTextBase,
11
+ zuiHashGeneratorSizes,
12
+ } from "../../design-system/hash-generator";
13
+
14
+ export const hashGeneratorVariants = cva(zuiHashGeneratorBase, {
15
+ variants: {
16
+ appearance: zuiHashGeneratorAppearances,
17
+ size: zuiHashGeneratorSizes,
18
+ },
19
+ defaultVariants: {
20
+ appearance: "default",
21
+ size: "md",
22
+ },
23
+ });
24
+
25
+ export const hashGeneratorHeaderVariants = cva(zuiHashGeneratorHeaderBase);
26
+ export const hashGeneratorLabelVariants = cva(zuiHashGeneratorLabelBase);
27
+ export const hashGeneratorInputVariants = cva(zuiHashGeneratorInputBase);
28
+ export const hashGeneratorOutputVariants = cva(zuiHashGeneratorOutputBase);
29
+ export const hashGeneratorOutputTextVariants = cva(
30
+ zuiHashGeneratorOutputTextBase,
31
+ );