@evinvest/uikit 0.29.4 → 0.31.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 (204) hide show
  1. package/README.md +294 -90
  2. package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
  3. package/dist/chunk-35SPIU4U.js.map +1 -0
  4. package/dist/components/account-list.js +128 -0
  5. package/dist/components/account-list.js.map +1 -0
  6. package/dist/components/account-menu-profile.js +114 -0
  7. package/dist/components/account-menu-profile.js.map +1 -0
  8. package/dist/components/account-menu.js +50 -23
  9. package/dist/components/account-menu.js.map +1 -1
  10. package/dist/components/account-settings-dialog.js +84 -0
  11. package/dist/components/account-settings-dialog.js.map +1 -0
  12. package/dist/components/alert-dialog.js +3 -2
  13. package/dist/components/alert-dialog.js.map +1 -1
  14. package/dist/components/animated-number-columns.js +105 -0
  15. package/dist/components/animated-number-columns.js.map +1 -0
  16. package/dist/components/animated-number.js +157 -0
  17. package/dist/components/animated-number.js.map +1 -0
  18. package/dist/components/app-shell.js +9 -0
  19. package/dist/components/app-shell.js.map +1 -1
  20. package/dist/components/aside.js +140 -0
  21. package/dist/components/aside.js.map +1 -0
  22. package/dist/components/badge.js +1 -1
  23. package/dist/components/badge.js.map +1 -1
  24. package/dist/components/button.js +3 -11
  25. package/dist/components/button.js.map +1 -1
  26. package/dist/components/chat-composer.js +141 -0
  27. package/dist/components/chat-composer.js.map +1 -0
  28. package/dist/components/chat-message.js +195 -0
  29. package/dist/components/chat-message.js.map +1 -0
  30. package/dist/components/chat-panel.js +137 -0
  31. package/dist/components/chat-panel.js.map +1 -0
  32. package/dist/components/chat-stream-text.js +69 -0
  33. package/dist/components/chat-stream-text.js.map +1 -0
  34. package/dist/components/chat-typing.js +26 -0
  35. package/dist/components/chat-typing.js.map +1 -0
  36. package/dist/components/command-palette.js +119 -0
  37. package/dist/components/command-palette.js.map +1 -0
  38. package/dist/components/command.js +3 -1
  39. package/dist/components/command.js.map +1 -1
  40. package/dist/components/confirm-dialog.js +123 -0
  41. package/dist/components/confirm-dialog.js.map +1 -0
  42. package/dist/components/context-menu.js +53 -5
  43. package/dist/components/context-menu.js.map +1 -1
  44. package/dist/components/dialog.js +3 -2
  45. package/dist/components/dialog.js.map +1 -1
  46. package/dist/components/drawer.js +3 -1
  47. package/dist/components/drawer.js.map +1 -1
  48. package/dist/components/dropdown-menu.js +64 -5
  49. package/dist/components/dropdown-menu.js.map +1 -1
  50. package/dist/components/form-row.js +51 -0
  51. package/dist/components/form-row.js.map +1 -0
  52. package/dist/components/header-controls.js +149 -0
  53. package/dist/components/header-controls.js.map +1 -0
  54. package/dist/components/icon-button.js +35 -0
  55. package/dist/components/icon-button.js.map +1 -0
  56. package/dist/components/key-value.js +68 -0
  57. package/dist/components/key-value.js.map +1 -0
  58. package/dist/components/menubar.js +39 -5
  59. package/dist/components/menubar.js.map +1 -1
  60. package/dist/components/native-select.js +3 -4
  61. package/dist/components/native-select.js.map +1 -1
  62. package/dist/components/nav-collapse.js +142 -0
  63. package/dist/components/nav-collapse.js.map +1 -0
  64. package/dist/components/nav-model.js.map +1 -1
  65. package/dist/components/navigation-menu.js +4 -5
  66. package/dist/components/navigation-menu.js.map +1 -1
  67. package/dist/components/page-frame.js +2 -5
  68. package/dist/components/page-frame.js.map +1 -1
  69. package/dist/components/popover-form.js +60 -0
  70. package/dist/components/popover-form.js.map +1 -0
  71. package/dist/components/resource-error.js +1 -1
  72. package/dist/components/resource-error.js.map +1 -1
  73. package/dist/components/section-nav.js +73 -20
  74. package/dist/components/section-nav.js.map +1 -1
  75. package/dist/components/select.js +16 -6
  76. package/dist/components/select.js.map +1 -1
  77. package/dist/components/settings.js +94 -0
  78. package/dist/components/settings.js.map +1 -0
  79. package/dist/components/sheet.js +3 -2
  80. package/dist/components/sheet.js.map +1 -1
  81. package/dist/components/shell-nav.js +80 -38
  82. package/dist/components/shell-nav.js.map +1 -1
  83. package/dist/components/smart-header.js +53 -0
  84. package/dist/components/smart-header.js.map +1 -0
  85. package/dist/components/status-item.js +42 -0
  86. package/dist/components/status-item.js.map +1 -0
  87. package/dist/components/system-banner.js +22 -12
  88. package/dist/components/system-banner.js.map +1 -1
  89. package/dist/components/top-bar.js +4 -3
  90. package/dist/components/top-bar.js.map +1 -1
  91. package/dist/components/ui-icons.js +77 -0
  92. package/dist/components/ui-icons.js.map +1 -0
  93. package/dist/components/use-count-tween.js +42 -0
  94. package/dist/components/use-count-tween.js.map +1 -0
  95. package/dist/components/workspace-tab.js +140 -0
  96. package/dist/components/workspace-tab.js.map +1 -0
  97. package/dist/generated/accent.js +2 -2
  98. package/dist/generated/accent.js.map +1 -1
  99. package/dist/generated/account-list.js +35 -0
  100. package/dist/generated/account-list.js.map +1 -0
  101. package/dist/generated/account-settings-dialog.js +27 -0
  102. package/dist/generated/account-settings-dialog.js.map +1 -0
  103. package/dist/generated/alert-dialog.js +1 -1
  104. package/dist/generated/alert-dialog.js.map +1 -1
  105. package/dist/generated/alert.js +2 -2
  106. package/dist/generated/alert.js.map +1 -1
  107. package/dist/generated/aside.js +36 -0
  108. package/dist/generated/aside.js.map +1 -0
  109. package/dist/generated/badge.js +7 -6
  110. package/dist/generated/badge.js.map +1 -1
  111. package/dist/generated/band.js +1 -1
  112. package/dist/generated/band.js.map +1 -1
  113. package/dist/generated/breadcrumb.js +2 -2
  114. package/dist/generated/breadcrumb.js.map +1 -1
  115. package/dist/generated/button-group.js +2 -2
  116. package/dist/generated/button-group.js.map +1 -1
  117. package/dist/generated/button.js +4 -4
  118. package/dist/generated/button.js.map +1 -1
  119. package/dist/generated/card.js +6 -6
  120. package/dist/generated/card.js.map +1 -1
  121. package/dist/generated/chat-composer.js +21 -0
  122. package/dist/generated/chat-composer.js.map +1 -0
  123. package/dist/generated/chat-message.js +86 -0
  124. package/dist/generated/chat-message.js.map +1 -0
  125. package/dist/generated/chat-typing.js +9 -0
  126. package/dist/generated/chat-typing.js.map +1 -0
  127. package/dist/generated/command-palette.js +17 -0
  128. package/dist/generated/command-palette.js.map +1 -0
  129. package/dist/generated/command.js +3 -3
  130. package/dist/generated/command.js.map +1 -1
  131. package/dist/generated/confirm-dialog.js +28 -0
  132. package/dist/generated/confirm-dialog.js.map +1 -0
  133. package/dist/generated/context-menu.js +1 -1
  134. package/dist/generated/context-menu.js.map +1 -1
  135. package/dist/generated/date-time-picker.js +1 -1
  136. package/dist/generated/date-time-picker.js.map +1 -1
  137. package/dist/generated/dialog.js +1 -1
  138. package/dist/generated/dialog.js.map +1 -1
  139. package/dist/generated/drawer.js +1 -1
  140. package/dist/generated/drawer.js.map +1 -1
  141. package/dist/generated/dropdown-menu.js +1 -1
  142. package/dist/generated/dropdown-menu.js.map +1 -1
  143. package/dist/generated/form-row.js +17 -0
  144. package/dist/generated/form-row.js.map +1 -0
  145. package/dist/generated/header-controls.js +56 -0
  146. package/dist/generated/header-controls.js.map +1 -0
  147. package/dist/generated/icon-button.js +13 -0
  148. package/dist/generated/icon-button.js.map +1 -0
  149. package/dist/generated/key-value.js +15 -0
  150. package/dist/generated/key-value.js.map +1 -0
  151. package/dist/generated/menubar.js +1 -1
  152. package/dist/generated/menubar.js.map +1 -1
  153. package/dist/generated/navigation-menu.js +1 -1
  154. package/dist/generated/navigation-menu.js.map +1 -1
  155. package/dist/generated/popover-form.js +13 -0
  156. package/dist/generated/popover-form.js.map +1 -0
  157. package/dist/generated/section-label.js +10 -0
  158. package/dist/generated/section-label.js.map +1 -0
  159. package/dist/generated/select.js +15 -3
  160. package/dist/generated/select.js.map +1 -1
  161. package/dist/generated/settings.js +36 -0
  162. package/dist/generated/settings.js.map +1 -0
  163. package/dist/generated/sheet.js +1 -1
  164. package/dist/generated/sheet.js.map +1 -1
  165. package/dist/generated/slider.js +1 -1
  166. package/dist/generated/slider.js.map +1 -1
  167. package/dist/generated/smart-header.js +11 -0
  168. package/dist/generated/smart-header.js.map +1 -0
  169. package/dist/generated/sonner.js +4 -4
  170. package/dist/generated/sonner.js.map +1 -1
  171. package/dist/generated/status-item.js +17 -0
  172. package/dist/generated/status-item.js.map +1 -0
  173. package/dist/generated/system-banner.js +33 -0
  174. package/dist/generated/system-banner.js.map +1 -0
  175. package/dist/generated/top-bar.js +9 -0
  176. package/dist/generated/top-bar.js.map +1 -0
  177. package/dist/generated/workspace-tab.js +17 -0
  178. package/dist/generated/workspace-tab.js.map +1 -0
  179. package/dist/index.d.ts +852 -156
  180. package/dist/index.js +21 -1
  181. package/dist/lib/cn.js +8 -1
  182. package/dist/lib/cn.js.map +1 -1
  183. package/dist/lib/number-parts.js +90 -0
  184. package/dist/lib/number-parts.js.map +1 -0
  185. package/dist/palette-cli.js +1 -1
  186. package/dist/palette.d.ts +6 -4
  187. package/dist/palette.js +3 -1
  188. package/dist/primitives/dismissable-layer.js +76 -4
  189. package/dist/primitives/dismissable-layer.js.map +1 -1
  190. package/dist/primitives/focus-scope.js +20 -5
  191. package/dist/primitives/focus-scope.js.map +1 -1
  192. package/dist/primitives/use-floating.js +16 -1
  193. package/dist/primitives/use-floating.js.map +1 -1
  194. package/dist/primitives/use-reduced-motion.js +20 -0
  195. package/dist/primitives/use-reduced-motion.js.map +1 -0
  196. package/package.json +1 -1
  197. package/styles/ev.css +281 -49
  198. package/styles/theme.css +335 -39
  199. package/styles/tokens.css +643 -103
  200. package/dist/chunk-3IPB4QQJ.js.map +0 -1
  201. package/dist/components/terminal.js +0 -206
  202. package/dist/components/terminal.js.map +0 -1
  203. package/dist/generated/terminal.js +0 -70
  204. package/dist/generated/terminal.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { buttonVariants } from \"./button\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer } from \"../primitives/dismissable-layer\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { usePresence } from \"../primitives/presence\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n ALERT_DIALOG_CONTENT,\n ALERT_DIALOG_DESCRIPTION,\n ALERT_DIALOG_FOOTER,\n ALERT_DIALOG_HEADER,\n ALERT_DIALOG_OVERLAY,\n ALERT_DIALOG_TITLE,\n} from \"../generated/alert-dialog\";\n\ninterface AlertDialogContextValue {\n open: boolean;\n setOpen: (next: boolean) => void;\n triggerRef: React.RefObject<HTMLElement | null>;\n}\n\nconst AlertDialogContext = React.createContext<AlertDialogContextValue | null>(null);\n\nfunction useAlertDialog(): AlertDialogContextValue {\n const ctx = React.useContext(AlertDialogContext);\n if (!ctx) throw new Error(\"AlertDialog parts must be used within <AlertDialog>\");\n return ctx;\n}\n\nexport interface AlertDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n children?: React.ReactNode;\n}\n\nexport function AlertDialog({ open, defaultOpen = false, onOpenChange, children }: AlertDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const triggerRef = React.useRef<HTMLElement | null>(null);\n return (\n <AlertDialogContext.Provider value={{ open: isOpen, setOpen, triggerRef }}>\n {children}\n </AlertDialogContext.Provider>\n );\n}\n\nexport interface AlertDialogTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function AlertDialogTrigger({ asChild = false, onClick, ...props }: AlertDialogTriggerProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"alert-dialog-trigger\"\n aria-expanded={open}\n ref={triggerRef as React.Ref<HTMLButtonElement>}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(true);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogOverlayProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {\n const { open } = useAlertDialog();\n // No onClick: the content's dismissable layer already closes on the\n // pointer-down that lands here, and a second path called onOpenChange(false)\n // twice for one press whenever the overlay was still hit by the click\n // (a host that closes later, CSS without the closed-state pointer-events).\n return (\n <div\n data-slot=\"alert-dialog-overlay\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(ALERT_DIALOG_OVERLAY, className)}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogContentProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogContent({ className, children, ...props }: AlertDialogContentProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const { isPresent, ref: presRef } = usePresence(open);\n const focusRef = useFocusScope(open, { returnFocusTo: triggerRef });\n const dismissRef = useDismissableLayer({\n enabled: open,\n onDismiss: () => setOpen(false),\n exclude: [triggerRef],\n modal: true,\n });\n if (!isPresent) return null;\n return (\n <Portal>\n <AlertDialogOverlay />\n <div\n data-slot=\"alert-dialog-content\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n data-state={open ? \"open\" : \"closed\"}\n ref={mergeRefs(focusRef, dismissRef, presRef as React.Ref<HTMLDivElement>)}\n className={cn(ALERT_DIALOG_CONTENT, className)}\n {...(props as Record<string, unknown>)}\n >\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </div>\n </Portal>\n );\n}\n\nexport function AlertDialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(ALERT_DIALOG_HEADER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(ALERT_DIALOG_FOOTER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogTitle({ className, ...props }: React.ComponentProps<\"h2\">) {\n return (\n <h2\n data-slot=\"alert-dialog-title\"\n className={cn(ALERT_DIALOG_TITLE, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <p\n data-slot=\"alert-dialog-description\"\n className={cn(ALERT_DIALOG_DESCRIPTION, className)}\n {...props}\n />\n );\n}\n\nexport interface AlertDialogActionProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogAction({ className, onClick, ...props }: AlertDialogActionProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-action\"\n className={cn(buttonVariants(), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogCancelProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogCancel({ className, onClick, ...props }: AlertDialogCancelProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-cancel\"\n className={cn(buttonVariants({ variant: \"outline\" }), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { buttonVariants } from \"./button\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer, useModalScrimStyle } from \"../primitives/dismissable-layer\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { usePresence } from \"../primitives/presence\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n ALERT_DIALOG_CONTENT,\n ALERT_DIALOG_DESCRIPTION,\n ALERT_DIALOG_FOOTER,\n ALERT_DIALOG_HEADER,\n ALERT_DIALOG_OVERLAY,\n ALERT_DIALOG_TITLE,\n} from \"../generated/alert-dialog\";\n\ninterface AlertDialogContextValue {\n open: boolean;\n setOpen: (next: boolean) => void;\n triggerRef: React.RefObject<HTMLElement | null>;\n}\n\nconst AlertDialogContext = React.createContext<AlertDialogContextValue | null>(null);\n\nfunction useAlertDialog(): AlertDialogContextValue {\n const ctx = React.useContext(AlertDialogContext);\n if (!ctx) throw new Error(\"AlertDialog parts must be used within <AlertDialog>\");\n return ctx;\n}\n\nexport interface AlertDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n children?: React.ReactNode;\n}\n\nexport function AlertDialog({ open, defaultOpen = false, onOpenChange, children }: AlertDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const triggerRef = React.useRef<HTMLElement | null>(null);\n return (\n <AlertDialogContext.Provider value={{ open: isOpen, setOpen, triggerRef }}>\n {children}\n </AlertDialogContext.Provider>\n );\n}\n\nexport interface AlertDialogTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function AlertDialogTrigger({ asChild = false, onClick, ...props }: AlertDialogTriggerProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"alert-dialog-trigger\"\n aria-expanded={open}\n ref={triggerRef as React.Ref<HTMLButtonElement>}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(true);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogOverlayProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {\n const { open } = useAlertDialog();\n // No onClick: the content's dismissable layer already closes on the\n // pointer-down that lands here, and a second path called onOpenChange(false)\n // twice for one press whenever the overlay was still hit by the click\n // (a host that closes later, CSS without the closed-state pointer-events).\n return (\n <div\n data-slot=\"alert-dialog-overlay\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(ALERT_DIALOG_OVERLAY, className)}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogContentProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogContent({ className, children, ...props }: AlertDialogContentProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const { isPresent, ref: presRef } = usePresence(open);\n const focusRef = useFocusScope(open, { returnFocusTo: triggerRef });\n const dismissRef = useDismissableLayer({\n enabled: open,\n onDismiss: () => setOpen(false),\n exclude: [triggerRef],\n modal: true,\n });\n const scrimStyle = useModalScrimStyle(dismissRef);\n if (!isPresent) return null;\n return (\n <Portal>\n <AlertDialogOverlay style={scrimStyle} />\n <div\n data-slot=\"alert-dialog-content\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n data-state={open ? \"open\" : \"closed\"}\n ref={mergeRefs(focusRef, dismissRef, presRef as React.Ref<HTMLDivElement>)}\n className={cn(ALERT_DIALOG_CONTENT, className)}\n {...(props as Record<string, unknown>)}\n >\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </div>\n </Portal>\n );\n}\n\nexport function AlertDialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(ALERT_DIALOG_HEADER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(ALERT_DIALOG_FOOTER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogTitle({ className, ...props }: React.ComponentProps<\"h2\">) {\n return (\n <h2\n data-slot=\"alert-dialog-title\"\n className={cn(ALERT_DIALOG_TITLE, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <p\n data-slot=\"alert-dialog-description\"\n className={cn(ALERT_DIALOG_DESCRIPTION, className)}\n {...props}\n />\n );\n}\n\nexport interface AlertDialogActionProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogAction({ className, onClick, ...props }: AlertDialogActionProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-action\"\n className={cn(buttonVariants(), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogCancelProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogCancel({ className, onClick, ...props }: AlertDialogCancelProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-cancel\"\n className={cn(buttonVariants({ variant: \"outline\" }), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,105 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { rollStep } from "../lib/number-parts.js";
5
+ const EDGE_MS = 350;
6
+ const EDGE_EASE = "var(--ev-ease-out, cubic-bezier(0.22, 1, 0.36, 1))";
7
+ const SPIN_MS = 750;
8
+ const SPRING = "linear(0, 0.097, 0.301, 0.521, 0.712, 0.855, 0.95, 1.005, 1.031, 1.038, 1.035, 1.028, 1.019, 1.012, 1.006, 1.002, 1, 0.999, 1, 1, 1)";
9
+ const WINDOW_EDGE = "0.25em";
10
+ const WINDOW_MASK = `linear-gradient(to bottom, transparent 0, black ${WINDOW_EDGE}, black calc(100% - ${WINDOW_EDGE}), transparent 100%)`;
11
+ function spinEasing() {
12
+ const springy = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("transition-timing-function", SPRING);
13
+ return springy ? SPRING : EDGE_EASE;
14
+ }
15
+ function Slot({ item, glyphs, direction, motion, spin }) {
16
+ const ref = React.useRef(null);
17
+ const [open, setOpen] = React.useState(!item.fresh);
18
+ React.useLayoutEffect(() => {
19
+ if (open) return;
20
+ void ref.current?.offsetWidth;
21
+ setOpen(true);
22
+ }, [open]);
23
+ const collapsed = item.exiting || !open;
24
+ const { part } = item;
25
+ return /* @__PURE__ */ jsx(
26
+ "span",
27
+ {
28
+ ref,
29
+ "data-slot": "animated-number-part",
30
+ "data-state": collapsed ? "collapsed" : "open",
31
+ style: {
32
+ display: "inline-grid",
33
+ gridTemplateColumns: collapsed ? "0fr" : "1fr",
34
+ opacity: collapsed ? 0 : 1,
35
+ transition: motion ? `grid-template-columns ${EDGE_MS}ms ${EDGE_EASE}, opacity ${EDGE_MS}ms ${EDGE_EASE}` : void 0
36
+ },
37
+ children: /* @__PURE__ */ jsx("span", { style: { minWidth: 0, overflowX: "clip", whiteSpace: "pre" }, children: part.kind === "digit" ? /* @__PURE__ */ jsx(Digit, { digit: part.digit, glyphs, direction, motion, spin }) : part.text })
38
+ }
39
+ );
40
+ }
41
+ const settle = (digit) => ({ digit, at: digit, lo: digit, hi: digit, animate: false, step: 0 });
42
+ const glyphIndex = (i) => (i % 10 + 10) % 10;
43
+ function Digit({ digit, glyphs, direction, motion, spin }) {
44
+ const [state, setState] = React.useState(() => settle(digit));
45
+ let s = state;
46
+ if (s.digit !== digit) {
47
+ if (motion) {
48
+ const step = rollStep(s.digit, digit, direction);
49
+ const at = s.at + step;
50
+ s = { digit, at, lo: Math.min(s.lo, at), hi: Math.max(s.hi, at), animate: true, step };
51
+ } else {
52
+ s = settle(digit);
53
+ }
54
+ setState(s);
55
+ }
56
+ const moving = s.lo !== s.hi;
57
+ const cells = Array.from({ length: s.hi - s.lo + 1 }, (_, n) => s.lo + n);
58
+ return /* @__PURE__ */ jsxs(
59
+ "span",
60
+ {
61
+ "data-slot": "animated-number-digit",
62
+ "data-roll": moving ? s.step > 0 ? "up" : "down" : "rest",
63
+ style: {
64
+ position: "relative",
65
+ display: "inline-block",
66
+ overflow: "clip",
67
+ paddingBlock: WINDOW_EDGE,
68
+ marginBlock: `-${WINDOW_EDGE}`,
69
+ ...moving ? { maskImage: WINDOW_MASK } : {}
70
+ },
71
+ children: [
72
+ /* @__PURE__ */ jsx("span", { style: { display: "block", visibility: "hidden" }, children: glyphs[0] }),
73
+ /* @__PURE__ */ jsx(
74
+ "span",
75
+ {
76
+ onTransitionEnd: (e) => {
77
+ if (e.target === e.currentTarget && e.propertyName === "transform") setState(settle(digit));
78
+ },
79
+ style: {
80
+ position: "absolute",
81
+ insetInline: 0,
82
+ top: WINDOW_EDGE,
83
+ transform: `translateY(calc(${-s.at} * 1lh))`,
84
+ transition: s.animate ? `transform ${spin}ms ${spinEasing()}` : "none"
85
+ },
86
+ children: cells.map((i) => /* @__PURE__ */ jsx(
87
+ "span",
88
+ {
89
+ style: { position: "absolute", insetInline: 0, top: `calc(${i} * 1lh)`, height: "1lh", textAlign: "center" },
90
+ children: glyphs[glyphIndex(i)]
91
+ },
92
+ i
93
+ ))
94
+ }
95
+ )
96
+ ]
97
+ }
98
+ );
99
+ }
100
+ export {
101
+ EDGE_MS,
102
+ SPIN_MS,
103
+ Slot
104
+ };
105
+ //# sourceMappingURL=animated-number-columns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/animated-number-columns.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport type { NumberItem, RollDirection } from \"../lib/number-parts\";\nimport { rollStep } from \"../lib/number-parts\";\n\n// The JS side has to know when an entering or leaving column is done, so these\n// are literals rather than `--ev-dur-*` (400ms is the nearest token).\nexport const EDGE_MS = 350;\nconst EDGE_EASE = \"var(--ev-ease-out, cubic-bezier(0.22, 1, 0.36, 1))\";\nexport const SPIN_MS = 750;\n// A damped spring (zeta .72, ~4% overshoot) sampled for `linear()`.\nconst SPRING =\n \"linear(0, 0.097, 0.301, 0.521, 0.712, 0.855, 0.95, 1.005, 1.031, 1.038, 1.035, 1.028, 1.019, 1.012, 1.006, 1.002, 1, 0.999, 1, 1, 1)\";\nconst WINDOW_EDGE = \"0.25em\";\nconst WINDOW_MASK = `linear-gradient(to bottom, transparent 0, black ${WINDOW_EDGE}, black calc(100% - ${WINDOW_EDGE}), transparent 100%)`;\n\nfunction spinEasing(): string {\n const springy = typeof CSS !== \"undefined\" && typeof CSS.supports === \"function\" && CSS.supports(\"transition-timing-function\", SPRING);\n return springy ? SPRING : EDGE_EASE;\n}\n\ninterface SlotProps {\n item: NumberItem;\n glyphs: readonly string[];\n direction: RollDirection;\n motion: boolean;\n spin: number;\n}\n\n/** One part of the number; collapses to zero width to enter or leave, so its neighbours slide. */\nexport function Slot({ item, glyphs, direction, motion, spin }: SlotProps) {\n const ref = React.useRef<HTMLSpanElement>(null);\n const [open, setOpen] = React.useState(!item.fresh);\n React.useLayoutEffect(() => {\n if (open) return;\n // Reading layout makes the browser style the collapsed state, so opening it is a transition.\n void ref.current?.offsetWidth;\n setOpen(true);\n }, [open]);\n const collapsed = item.exiting || !open;\n const { part } = item;\n return (\n <span\n ref={ref}\n data-slot=\"animated-number-part\"\n data-state={collapsed ? \"collapsed\" : \"open\"}\n style={{\n display: \"inline-grid\",\n gridTemplateColumns: collapsed ? \"0fr\" : \"1fr\",\n opacity: collapsed ? 0 : 1,\n transition: motion\n ? `grid-template-columns ${EDGE_MS}ms ${EDGE_EASE}, opacity ${EDGE_MS}ms ${EDGE_EASE}`\n : undefined,\n }}\n >\n <span style={{ minWidth: 0, overflowX: \"clip\", whiteSpace: \"pre\" }}>\n {part.kind === \"digit\" ? (\n <Digit digit={part.digit} glyphs={glyphs} direction={direction} motion={motion} spin={spin} />\n ) : (\n part.text\n )}\n </span>\n </span>\n );\n}\n\ninterface SpinState {\n digit: number;\n /** Index into the unbounded strip; the glyph at index i is `i mod 10`. */\n at: number;\n lo: number;\n hi: number;\n animate: boolean;\n /** Sign of the last move, for `data-roll`. */\n step: number;\n}\n\nconst settle = (digit: number): SpinState => ({ digit, at: digit, lo: digit, hi: digit, animate: false, step: 0 });\n\nconst glyphIndex = (i: number): number => ((i % 10) + 10) % 10;\n\ninterface DigitProps {\n digit: number;\n glyphs: readonly string[];\n direction: RollDirection;\n motion: boolean;\n spin: number;\n}\n\n/**\n * A clipped one-line window over a strip of digits. The strip is unbounded,\n * so a forced direction wraps (9 -> 0 going up lands on index 10) and a column\n * never rewinds through the digits in between. Only the cells between where it\n * was and where it is going exist in the DOM; when the transition ends it is\n * rebased to 0..9 without a transition.\n */\nfunction Digit({ digit, glyphs, direction, motion, spin }: DigitProps) {\n const [state, setState] = React.useState<SpinState>(() => settle(digit));\n let s = state;\n if (s.digit !== digit) {\n if (motion) {\n const step = rollStep(s.digit, digit, direction);\n const at = s.at + step;\n s = { digit, at, lo: Math.min(s.lo, at), hi: Math.max(s.hi, at), animate: true, step };\n } else {\n s = settle(digit);\n }\n setState(s);\n }\n const moving = s.lo !== s.hi;\n const cells = Array.from({ length: s.hi - s.lo + 1 }, (_, n) => s.lo + n);\n return (\n <span\n data-slot=\"animated-number-digit\"\n data-roll={moving ? (s.step > 0 ? \"up\" : \"down\") : \"rest\"}\n style={{\n position: \"relative\",\n display: \"inline-block\",\n overflow: \"clip\",\n paddingBlock: WINDOW_EDGE,\n marginBlock: `-${WINDOW_EDGE}`,\n ...(moving ? { maskImage: WINDOW_MASK } : {}),\n }}\n >\n <span style={{ display: \"block\", visibility: \"hidden\" }}>{glyphs[0]}</span>\n <span\n onTransitionEnd={(e) => {\n if (e.target === e.currentTarget && e.propertyName === \"transform\") setState(settle(digit));\n }}\n style={{\n position: \"absolute\",\n insetInline: 0,\n top: WINDOW_EDGE,\n transform: `translateY(calc(${-s.at} * 1lh))`,\n transition: s.animate ? `transform ${spin}ms ${spinEasing()}` : \"none\",\n }}\n >\n {cells.map((i) => (\n <span\n key={i}\n style={{ position: \"absolute\", insetInline: 0, top: `calc(${i} * 1lh)`, height: \"1lh\", textAlign: \"center\" }}\n >\n {glyphs[glyphIndex(i)]}\n </span>\n ))}\n </span>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import {
6
+ digitGlyphs,
7
+ mergeItems,
8
+ resolveDirection,
9
+ splitNumber
10
+ } from "../lib/number-parts.js";
11
+ import { useReducedMotion } from "../primitives/use-reduced-motion.js";
12
+ import { EDGE_MS, SPIN_MS, Slot } from "./animated-number-columns.js";
13
+ import { useCountTween } from "./use-count-tween.js";
14
+ function AnimatedNumber({
15
+ value,
16
+ format,
17
+ locales,
18
+ mode = "roll",
19
+ trend,
20
+ duration,
21
+ prefix,
22
+ suffix,
23
+ live = false,
24
+ animateOnMount = false,
25
+ from = 0,
26
+ className,
27
+ style,
28
+ ...props
29
+ }) {
30
+ const formatKey = JSON.stringify(format ?? null);
31
+ const localeKey = Array.isArray(locales) ? locales.join(",") : String(locales ?? "");
32
+ const formatter = React.useMemo(
33
+ () => new Intl.NumberFormat(locales, format),
34
+ [localeKey, formatKey]
35
+ );
36
+ const text = formatter.format(value);
37
+ return /* @__PURE__ */ jsxs(
38
+ "span",
39
+ {
40
+ "data-slot": "animated-number",
41
+ "data-mode": mode,
42
+ className: cn("relative inline-flex items-baseline tabular-nums", className),
43
+ style,
44
+ ...props,
45
+ children: [
46
+ prefix,
47
+ /* @__PURE__ */ jsx(
48
+ "span",
49
+ {
50
+ "data-slot": "animated-number-text",
51
+ className: "sr-only",
52
+ "aria-live": live ? "polite" : void 0,
53
+ "aria-atomic": live ? true : void 0,
54
+ children: text
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "inline-flex select-none items-baseline", children: mode === "count" ? /* @__PURE__ */ jsx(
58
+ CountView,
59
+ {
60
+ value,
61
+ formatter,
62
+ from,
63
+ animateOnMount,
64
+ ...duration === void 0 ? {} : { duration }
65
+ }
66
+ ) : /* @__PURE__ */ jsx(
67
+ RollView,
68
+ {
69
+ value,
70
+ formatter,
71
+ from,
72
+ animateOnMount,
73
+ spin: duration ?? SPIN_MS,
74
+ ...trend === void 0 ? {} : { trend }
75
+ }
76
+ ) }),
77
+ suffix
78
+ ]
79
+ }
80
+ );
81
+ }
82
+ function CountView({ value, formatter, from, animateOnMount, duration }) {
83
+ const shown = useCountTween(value, {
84
+ from,
85
+ animateOnMount,
86
+ ...duration === void 0 ? {} : { duration }
87
+ });
88
+ return /* @__PURE__ */ jsx("span", { children: formatter.format(shown) });
89
+ }
90
+ function useMountLead(enabled) {
91
+ const [lead, setLead] = React.useState(false);
92
+ const started = React.useRef(false);
93
+ React.useLayoutEffect(() => {
94
+ if (!enabled || started.current) return;
95
+ started.current = true;
96
+ setLead(true);
97
+ let inner = 0;
98
+ const outer = requestAnimationFrame(() => {
99
+ inner = requestAnimationFrame(() => setLead(false));
100
+ });
101
+ return () => {
102
+ cancelAnimationFrame(outer);
103
+ cancelAnimationFrame(inner);
104
+ started.current = false;
105
+ setLead(false);
106
+ };
107
+ }, [enabled]);
108
+ return lead;
109
+ }
110
+ function RollView({ value, formatter, trend, spin, from, animateOnMount }) {
111
+ const motion = !useReducedMotion();
112
+ const lead = useMountLead(animateOnMount && motion);
113
+ const target = lead ? from : value;
114
+ const glyphs = React.useMemo(() => digitGlyphs(formatter), [formatter]);
115
+ const [state, setState] = React.useState(() => ({
116
+ formatter,
117
+ value: target,
118
+ direction: 0,
119
+ items: splitNumber(formatter, target, glyphs).map((part) => ({ part, exiting: false, fresh: false }))
120
+ }));
121
+ let current = state;
122
+ if (state.formatter !== formatter || state.value !== target) {
123
+ const sameFormat = state.formatter === formatter;
124
+ const animate = motion && sameFormat && !lead;
125
+ current = {
126
+ formatter,
127
+ value: target,
128
+ direction: sameFormat ? resolveDirection(trend, state.value, target) : 0,
129
+ items: mergeItems(state.items, splitNumber(formatter, target, glyphs), animate)
130
+ };
131
+ setState(current);
132
+ }
133
+ const leaving = current.items.some((item) => item.exiting);
134
+ React.useEffect(() => {
135
+ if (!leaving) return;
136
+ const id = setTimeout(
137
+ () => setState((s) => ({ ...s, items: s.items.filter((item) => !item.exiting) })),
138
+ EDGE_MS + 50
139
+ );
140
+ return () => clearTimeout(id);
141
+ }, [leaving, current.items]);
142
+ return /* @__PURE__ */ jsx(Fragment, { children: current.items.map((item) => /* @__PURE__ */ jsx(
143
+ Slot,
144
+ {
145
+ item,
146
+ glyphs,
147
+ direction: current.direction,
148
+ motion,
149
+ spin
150
+ },
151
+ item.part.key
152
+ )) });
153
+ }
154
+ export {
155
+ AnimatedNumber
156
+ };
157
+ //# sourceMappingURL=animated-number.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/animated-number.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n digitGlyphs,\n mergeItems,\n resolveDirection,\n splitNumber,\n type NumberItem,\n type RollDirection,\n} from \"../lib/number-parts\";\nimport { useReducedMotion } from \"../primitives/use-reduced-motion\";\nimport { EDGE_MS, SPIN_MS, Slot } from \"./animated-number-columns\";\nimport { useCountTween } from \"./use-count-tween\";\n\nexport interface AnimatedNumberProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\" | \"prefix\"> {\n value: number;\n /** Passed to `Intl.NumberFormat`: currency, percent, compact, `signDisplay`... */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** `roll`: digit columns spin. `count`: the number is tweened and re-formatted each frame. Default `roll`. */\n mode?: \"roll\" | \"count\";\n /**\n * Roll only. `1` every column spins up, `-1` down, `0` each column takes its\n * shorter way. Default: up when the value rose, down when it fell.\n */\n trend?: RollDirection;\n /** Milliseconds one change takes: the spin in `roll` (default 750), the whole tween in `count` (default 800). */\n duration?: number;\n prefix?: React.ReactNode;\n suffix?: React.ReactNode;\n /** Announce each new value politely. Off by default: a ticker would talk over the page. */\n live?: boolean;\n /** Animate the first render from `from`. The server markup still prints `value`. */\n animateOnMount?: boolean;\n /** The first-render start with `animateOnMount`. Default `0`. */\n from?: number;\n}\n\n/**\n * A number that animates when `value` changes. `roll` (default) spins each\n * digit column like an odometer; `count` tweens the number itself. Both format\n * with `Intl.NumberFormat`, inherit font and colour, and swap instantly under\n * `prefers-reduced-motion`. The final formatted value is in the DOM once, as\n * text for assistive tech and for copying; the moving glyphs are `aria-hidden`.\n */\nexport function AnimatedNumber({\n value,\n format,\n locales,\n mode = \"roll\",\n trend,\n duration,\n prefix,\n suffix,\n live = false,\n animateOnMount = false,\n from = 0,\n className,\n style,\n ...props\n}: AnimatedNumberProps) {\n // Callers pass `format` inline; keying on its content keeps one formatter per distinct options.\n const formatKey = JSON.stringify(format ?? null);\n const localeKey = Array.isArray(locales) ? locales.join(\",\") : String(locales ?? \"\");\n const formatter = React.useMemo(\n () => new Intl.NumberFormat(locales, format),\n [localeKey, formatKey],\n );\n const text = formatter.format(value);\n return (\n <span\n data-slot=\"animated-number\"\n data-mode={mode}\n className={cn(\"relative inline-flex items-baseline tabular-nums\", className)}\n style={style}\n {...props}\n >\n {prefix}\n <span\n data-slot=\"animated-number-text\"\n className=\"sr-only\"\n aria-live={live ? \"polite\" : undefined}\n aria-atomic={live ? true : undefined}\n >\n {text}\n </span>\n <span aria-hidden className=\"inline-flex select-none items-baseline\">\n {mode === \"count\" ? (\n <CountView\n value={value}\n formatter={formatter}\n from={from}\n animateOnMount={animateOnMount}\n {...(duration === undefined ? {} : { duration })}\n />\n ) : (\n <RollView\n value={value}\n formatter={formatter}\n from={from}\n animateOnMount={animateOnMount}\n spin={duration ?? SPIN_MS}\n {...(trend === undefined ? {} : { trend })}\n />\n )}\n </span>\n {suffix}\n </span>\n );\n}\n\ninterface CountViewProps {\n value: number;\n formatter: Intl.NumberFormat;\n from: number;\n animateOnMount: boolean;\n duration?: number;\n}\n\nfunction CountView({ value, formatter, from, animateOnMount, duration }: CountViewProps) {\n const shown = useCountTween(value, {\n from,\n animateOnMount,\n ...(duration === undefined ? {} : { duration }),\n });\n return <span>{formatter.format(shown)}</span>;\n}\n\ninterface RollState {\n formatter: Intl.NumberFormat;\n value: number;\n direction: RollDirection;\n items: NumberItem[];\n}\n\ninterface RollViewProps {\n value: number;\n formatter: Intl.NumberFormat;\n trend?: RollDirection;\n spin: number;\n from: number;\n animateOnMount: boolean;\n}\n\n// With `animateOnMount` the first client commit shows `from` and the value\n// arrives two frames later: one frame is not enough for the browser to have\n// painted `from` and so have a start for the transition.\nfunction useMountLead(enabled: boolean): boolean {\n const [lead, setLead] = React.useState(false);\n const started = React.useRef(false);\n React.useLayoutEffect(() => {\n if (!enabled || started.current) return;\n started.current = true;\n setLead(true);\n let inner = 0;\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(() => setLead(false));\n });\n return () => {\n cancelAnimationFrame(outer);\n cancelAnimationFrame(inner);\n started.current = false;\n setLead(false);\n };\n }, [enabled]);\n return lead;\n}\n\nfunction RollView({ value, formatter, trend, spin, from, animateOnMount }: RollViewProps) {\n const motion = !useReducedMotion();\n const lead = useMountLead(animateOnMount && motion);\n const target = lead ? from : value;\n const glyphs = React.useMemo(() => digitGlyphs(formatter), [formatter]);\n const [state, setState] = React.useState<RollState>(() => ({\n formatter,\n value: target,\n direction: 0,\n items: splitNumber(formatter, target, glyphs).map((part) => ({ part, exiting: false, fresh: false })),\n }));\n\n let current = state;\n if (state.formatter !== formatter || state.value !== target) {\n const sameFormat = state.formatter === formatter;\n // `lead` is a jump to `from`, not a change the reader should watch.\n const animate = motion && sameFormat && !lead;\n current = {\n formatter,\n value: target,\n direction: sameFormat ? resolveDirection(trend, state.value, target) : 0,\n items: mergeItems(state.items, splitNumber(formatter, target, glyphs), animate),\n };\n setState(current);\n }\n\n const leaving = current.items.some((item) => item.exiting);\n React.useEffect(() => {\n if (!leaving) return;\n const id = setTimeout(\n () => setState((s) => ({ ...s, items: s.items.filter((item) => !item.exiting) })),\n EDGE_MS + 50,\n );\n return () => clearTimeout(id);\n }, [leaving, current.items]);\n\n return (\n <>\n {current.items.map((item) => (\n <Slot\n key={item.part.key}\n item={item}\n glyphs={glyphs}\n direction={current.direction}\n motion={motion}\n spin={spin}\n />\n ))}\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -23,6 +23,7 @@ function AppShell({
23
23
  tabBar,
24
24
  topBar,
25
25
  banner,
26
+ aside,
26
27
  breakpoint = "lg",
27
28
  mainProps,
28
29
  className,
@@ -78,6 +79,14 @@ function AppShell({
78
79
  ]
79
80
  }
80
81
  ),
82
+ present(aside) && /* @__PURE__ */ jsx(
83
+ "div",
84
+ {
85
+ "data-slot": "app-shell-aside",
86
+ className: "sticky top-[var(--ev-shell-offset,0px)] flex h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start",
87
+ children: aside
88
+ }
89
+ ),
81
90
  present(tabBar) && /* @__PURE__ */ jsx(
82
91
  "div",
83
92
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/app-shell.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/** Where the shell swaps the bottom tab bar for the rail. */\nexport type ShellBreakpoint = \"md\" | \"lg\";\n\nexport interface AppShellProps extends React.ComponentProps<\"div\"> {\n /** The desktop rail — a `ShellNav`. Shown from `breakpoint` up. */\n rail?: React.ReactNode;\n /** The phone's tab bar — a `BottomTabBar`. Pinned to the viewport bottom below `breakpoint`. */\n tabBar?: React.ReactNode;\n /** A `TopBar`, pinned over the column from `breakpoint` up (a phone titles its screen with `MobileAppBar`). */\n topBar?: React.ReactNode;\n /** Strips above the page (`SystemBanner`s). An empty slot takes no room. */\n banner?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** Props for the `<main>` the page renders into (an `id` for a skip link, say). */\n mainProps?: React.ComponentProps<\"main\">;\n}\n\n// Tailwind only sees whole class strings, so each breakpoint spells its own.\nconst AT = {\n md: {\n rail: \"hidden md:flex\",\n tabBar: \"md:hidden\",\n topBar: \"hidden md:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] md:pb-0\",\n banner: \"md:px-8\",\n },\n lg: {\n rail: \"hidden lg:flex\",\n tabBar: \"lg:hidden\",\n topBar: \"hidden lg:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] lg:pb-0\",\n banner: \"lg:px-8\",\n },\n} as const;\n\nconst present = (node: React.ReactNode) => node !== null && node !== undefined && node !== false;\n\n/**\n * The signed-in app's frame: a rail beside a content column on a wide screen, a\n * bottom tab bar under it on a phone. Layout only — the nav, the top bar, the banners\n * and the page are slots, so the shell knows nothing about routes, roles or data.\n *\n * Fills the viewport less `--ev-shell-offset` (the room a host shell's own header\n * takes); the rail sticks under that offset at full remaining height and scrolls\n * by itself when it cannot fit. The column reserves the tab bar's height plus\n * the bottom safe area, so the bar never covers content.\n */\nexport function AppShell({\n rail,\n tabBar,\n topBar,\n banner,\n breakpoint = \"lg\",\n mainProps,\n className,\n children,\n ...props\n}: AppShellProps) {\n const at = AT[breakpoint];\n return (\n <div\n data-slot=\"app-shell\"\n data-breakpoint={breakpoint}\n className={cn(\n \"flex min-h-[calc(100dvh-var(--ev-shell-offset,0px))] bg-background\",\n className,\n )}\n {...props}\n >\n {present(rail) && (\n <div\n data-slot=\"app-shell-rail\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\",\n at.rail,\n )}\n >\n {rail}\n </div>\n )}\n <div\n data-slot=\"app-shell-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col\", present(tabBar) && at.reserve)}\n >\n {present(topBar) && (\n <div\n data-slot=\"app-shell-top-bar\"\n className={cn(\"sticky top-[var(--ev-shell-offset,0px)] z-30\", at.topBar)}\n >\n {topBar}\n </div>\n )}\n {present(banner) && (\n <div\n data-slot=\"app-shell-banner\"\n className={cn(\"flex flex-col gap-2 px-4 pt-4 empty:hidden\", at.banner)}\n >\n {banner}\n </div>\n )}\n <main {...mainProps} className={cn(\"min-w-0 flex-1\", mainProps?.className)}>\n {children}\n </main>\n </div>\n {present(tabBar) && (\n <div\n data-slot=\"app-shell-tab-bar\"\n className={cn(\"fixed inset-x-0 bottom-0 z-40\", at.tabBar)}\n >\n {tabBar}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"AA0EQ,cAUF,YAVE;AA1ER,YAAY,WAAW;AACvB,SAAS,UAAU;AAqBnB,MAAM,KAAK;AAAA,EACT,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,UAAU,CAAC,SAA0B,SAAS,QAAQ,SAAS,UAAa,SAAS;AAYpF,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,KAAK,GAAG,UAAU;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,KACX;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,GAAG;AAAA,YACL;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,gCAAgC,QAAQ,MAAM,KAAK,GAAG,OAAO;AAAA,YAE1E;AAAA,sBAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,gDAAgD,GAAG,MAAM;AAAA,kBAEtE;AAAA;AAAA,cACH;AAAA,cAED,QAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,8CAA8C,GAAG,MAAM;AAAA,kBAEpE;AAAA;AAAA,cACH;AAAA,cAEF,oBAAC,UAAM,GAAG,WAAW,WAAW,GAAG,kBAAkB,WAAW,SAAS,GACtE,UACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,QAAQ,MAAM,KACb;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,iCAAiC,GAAG,MAAM;AAAA,YAEvD;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/app-shell.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/** Where the shell swaps the bottom tab bar for the rail. */\nexport type ShellBreakpoint = \"md\" | \"lg\";\n\nexport interface AppShellProps extends React.ComponentProps<\"div\"> {\n /** The desktop rail — a `ShellNav`. Shown from `breakpoint` up. */\n rail?: React.ReactNode;\n /** The phone's tab bar — a `BottomTabBar`. Pinned to the viewport bottom below `breakpoint`. */\n tabBar?: React.ReactNode;\n /** A `TopBar`, pinned over the column from `breakpoint` up (a phone titles its screen with `MobileAppBar`). */\n topBar?: React.ReactNode;\n /** Strips above the page (`SystemBanner`s). An empty slot takes no room. */\n banner?: React.ReactNode;\n /** Right of the content column: an `AsidePanel` and its `AsideRail`, side by side. Sticks like the rail. */\n aside?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** Props for the `<main>` the page renders into (an `id` for a skip link, say). */\n mainProps?: React.ComponentProps<\"main\">;\n}\n\n// Tailwind only sees whole class strings, so each breakpoint spells its own.\nconst AT = {\n md: {\n rail: \"hidden md:flex\",\n tabBar: \"md:hidden\",\n topBar: \"hidden md:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] md:pb-0\",\n banner: \"md:px-8\",\n },\n lg: {\n rail: \"hidden lg:flex\",\n tabBar: \"lg:hidden\",\n topBar: \"hidden lg:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] lg:pb-0\",\n banner: \"lg:px-8\",\n },\n} as const;\n\nconst present = (node: React.ReactNode) => node !== null && node !== undefined && node !== false;\n\n/**\n * The signed-in app's frame: a rail beside a content column on a wide screen, a\n * bottom tab bar under it on a phone. Layout only — the nav, the top bar, the banners\n * and the page are slots, so the shell knows nothing about routes, roles or data.\n *\n * Fills the viewport less `--ev-shell-offset` (the room a host shell's own header\n * takes); the rail sticks under that offset at full remaining height and scrolls\n * by itself when it cannot fit. The column reserves the tab bar's height plus\n * the bottom safe area, so the bar never covers content.\n */\nexport function AppShell({\n rail,\n tabBar,\n topBar,\n banner,\n aside,\n breakpoint = \"lg\",\n mainProps,\n className,\n children,\n ...props\n}: AppShellProps) {\n const at = AT[breakpoint];\n return (\n <div\n data-slot=\"app-shell\"\n data-breakpoint={breakpoint}\n className={cn(\n \"flex min-h-[calc(100dvh-var(--ev-shell-offset,0px))] bg-background\",\n className,\n )}\n {...props}\n >\n {present(rail) && (\n <div\n data-slot=\"app-shell-rail\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\",\n at.rail,\n )}\n >\n {rail}\n </div>\n )}\n <div\n data-slot=\"app-shell-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col\", present(tabBar) && at.reserve)}\n >\n {present(topBar) && (\n <div\n data-slot=\"app-shell-top-bar\"\n className={cn(\"sticky top-[var(--ev-shell-offset,0px)] z-30\", at.topBar)}\n >\n {topBar}\n </div>\n )}\n {present(banner) && (\n <div\n data-slot=\"app-shell-banner\"\n className={cn(\"flex flex-col gap-2 px-4 pt-4 empty:hidden\", at.banner)}\n >\n {banner}\n </div>\n )}\n <main {...mainProps} className={cn(\"min-w-0 flex-1\", mainProps?.className)}>\n {children}\n </main>\n </div>\n {present(aside) && (\n <div\n data-slot=\"app-shell-aside\"\n className=\"sticky top-[var(--ev-shell-offset,0px)] flex h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\"\n >\n {aside}\n </div>\n )}\n {present(tabBar) && (\n <div\n data-slot=\"app-shell-tab-bar\"\n className={cn(\"fixed inset-x-0 bottom-0 z-40\", at.tabBar)}\n >\n {tabBar}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"AA6EQ,cAUF,YAVE;AA7ER,YAAY,WAAW;AACvB,SAAS,UAAU;AAuBnB,MAAM,KAAK;AAAA,EACT,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,UAAU,CAAC,SAA0B,SAAS,QAAQ,SAAS,UAAa,SAAS;AAYpF,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,KAAK,GAAG,UAAU;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,KACX;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,GAAG;AAAA,YACL;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,gCAAgC,QAAQ,MAAM,KAAK,GAAG,OAAO;AAAA,YAE1E;AAAA,sBAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,gDAAgD,GAAG,MAAM;AAAA,kBAEtE;AAAA;AAAA,cACH;AAAA,cAED,QAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,8CAA8C,GAAG,MAAM;AAAA,kBAEpE;AAAA;AAAA,cACH;AAAA,cAEF,oBAAC,UAAM,GAAG,WAAW,WAAW,GAAG,kBAAkB,WAAW,SAAS,GACtE,UACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,QAAQ,KAAK,KACZ;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QAED,QAAQ,MAAM,KACb;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,iCAAiC,GAAG,MAAM;AAAA,YAEvD;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -0,0 +1,140 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import {
6
+ ASIDE_BODY,
7
+ ASIDE_FOOTER,
8
+ ASIDE_HEADER_BACK_ICON,
9
+ ASIDE_HEADER_BASE,
10
+ ASIDE_HEADER_TITLE,
11
+ ASIDE_PANEL_DOCKED,
12
+ ASIDE_PANEL_DRAWER,
13
+ ASIDE_PANEL_SHEET,
14
+ ASIDE_RAIL_BASE,
15
+ ASIDE_RAIL_SPACER,
16
+ asidePanelSideClasses
17
+ } from "../generated/aside.js";
18
+ import { Drawer, DrawerContent } from "./drawer.js";
19
+ import { IconButton } from "./icon-button.js";
20
+ import { Sheet, SheetContent } from "./sheet.js";
21
+ import { UiIcon } from "./ui-icons.js";
22
+ function AsideHeader({ title, onBack, onClose, actions, labels = {}, className, ...props }) {
23
+ return /* @__PURE__ */ jsxs(
24
+ "div",
25
+ {
26
+ "data-slot": "aside-header",
27
+ className: cn(ASIDE_HEADER_BASE, className),
28
+ ...props,
29
+ children: [
30
+ onBack && /* @__PURE__ */ jsx(IconButton, { "aria-label": labels.back ?? "Back", size: "sm", onClick: onBack, children: /* @__PURE__ */ jsx(UiIcon, { name: "chevron-right", className: ASIDE_HEADER_BACK_ICON }) }),
31
+ /* @__PURE__ */ jsx("h2", { className: ASIDE_HEADER_TITLE, children: title }),
32
+ actions,
33
+ onClose && /* @__PURE__ */ jsx(IconButton, { "aria-label": labels.close ?? "Close", size: "sm", onClick: onClose, children: /* @__PURE__ */ jsx(UiIcon, { name: "x" }) })
34
+ ]
35
+ }
36
+ );
37
+ }
38
+ function AsideRail({
39
+ items,
40
+ endItems = [],
41
+ value,
42
+ onValueChange,
43
+ className,
44
+ "aria-label": ariaLabel = "Panels",
45
+ ...props
46
+ }) {
47
+ const button = (item) => /* @__PURE__ */ jsx(
48
+ IconButton,
49
+ {
50
+ "aria-label": item.label,
51
+ tooltip: item.label,
52
+ pressed: value === item.id,
53
+ onClick: () => onValueChange(value === item.id ? null : item.id),
54
+ children: item.icon
55
+ },
56
+ item.id
57
+ );
58
+ return /* @__PURE__ */ jsxs(
59
+ "div",
60
+ {
61
+ role: "group",
62
+ "aria-label": ariaLabel,
63
+ "data-slot": "aside-rail",
64
+ className: cn(ASIDE_RAIL_BASE, className),
65
+ ...props,
66
+ children: [
67
+ items.map(button),
68
+ /* @__PURE__ */ jsx("span", { className: ASIDE_RAIL_SPACER }),
69
+ endItems.map(button)
70
+ ]
71
+ }
72
+ );
73
+ }
74
+ function useAtLeast(query) {
75
+ return React.useSyncExternalStore(
76
+ (notify) => {
77
+ if (typeof window.matchMedia !== "function") return () => {
78
+ };
79
+ const mq = window.matchMedia(query);
80
+ mq.addEventListener("change", notify);
81
+ return () => mq.removeEventListener("change", notify);
82
+ },
83
+ () => typeof window.matchMedia !== "function" || window.matchMedia(query).matches,
84
+ // Server and first paint assume a wide screen; a phone swaps to the drawer on hydrate.
85
+ () => true
86
+ );
87
+ }
88
+ function AsidePanel({
89
+ mode = "docked",
90
+ side = "right",
91
+ open = true,
92
+ onOpenChange,
93
+ header,
94
+ footer,
95
+ collapseBelow = "md",
96
+ className,
97
+ children,
98
+ ...props
99
+ }) {
100
+ const viewportWide = useAtLeast("(min-width: 768px)");
101
+ const wide = collapseBelow === false || viewportWide;
102
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
103
+ header,
104
+ /* @__PURE__ */ jsx("div", { "data-slot": "aside-body", className: ASIDE_BODY, children }),
105
+ footer != null && /* @__PURE__ */ jsx("div", { "data-slot": "aside-footer", className: ASIDE_FOOTER, children: footer })
106
+ ] });
107
+ if (mode === "docked" && wide) {
108
+ if (!open) return null;
109
+ return /* @__PURE__ */ jsx(
110
+ "aside",
111
+ {
112
+ "data-slot": "aside-panel",
113
+ "data-mode": "docked",
114
+ "data-side": side,
115
+ className: cn(ASIDE_PANEL_DOCKED, asidePanelSideClasses[side], className),
116
+ ...props,
117
+ children: body
118
+ }
119
+ );
120
+ }
121
+ const label = props["aria-label"];
122
+ if (!wide) {
123
+ return /* @__PURE__ */ jsx(Drawer, { open, ...onOpenChange ? { onOpenChange } : {}, direction: "bottom", children: /* @__PURE__ */ jsx(DrawerContent, { "aria-label": label, className: ASIDE_PANEL_DRAWER, children: body }) });
124
+ }
125
+ return /* @__PURE__ */ jsx(Sheet, { open, ...onOpenChange ? { onOpenChange } : {}, children: /* @__PURE__ */ jsx(
126
+ SheetContent,
127
+ {
128
+ side,
129
+ "aria-label": label,
130
+ className: cn(ASIDE_PANEL_SHEET, className),
131
+ children: body
132
+ }
133
+ ) });
134
+ }
135
+ export {
136
+ AsideHeader,
137
+ AsidePanel,
138
+ AsideRail
139
+ };
140
+ //# sourceMappingURL=aside.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/aside.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n ASIDE_BODY,\n ASIDE_FOOTER,\n ASIDE_HEADER_BACK_ICON,\n ASIDE_HEADER_BASE,\n ASIDE_HEADER_TITLE,\n ASIDE_PANEL_DOCKED,\n ASIDE_PANEL_DRAWER,\n ASIDE_PANEL_SHEET,\n ASIDE_RAIL_BASE,\n ASIDE_RAIL_SPACER,\n asidePanelSideClasses,\n type AsidePanelSide,\n} from \"../generated/aside\";\nimport { Drawer, DrawerContent } from \"./drawer\";\nimport { IconButton } from \"./icon-button\";\nimport { Sheet, SheetContent } from \"./sheet\";\nimport { UiIcon } from \"./ui-icons\";\n\nexport interface AsideHeaderLabels {\n back?: string;\n close?: string;\n}\n\nexport interface AsideHeaderProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n title: React.ReactNode;\n /** Shows the back button first. */\n onBack?: () => void;\n /** Shows the close button last. */\n onClose?: () => void;\n /** `IconButton`s between the title and the close button: pin, more. */\n actions?: React.ReactNode;\n labels?: AsideHeaderLabels;\n}\n\n/** The 44 px title row of an aside. */\nexport function AsideHeader({ title, onBack, onClose, actions, labels = {}, className, ...props }: AsideHeaderProps) {\n return (\n <div\n data-slot=\"aside-header\"\n className={cn(ASIDE_HEADER_BASE, className)}\n {...props}\n >\n {onBack && (\n <IconButton aria-label={labels.back ?? \"Back\"} size=\"sm\" onClick={onBack}>\n <UiIcon name=\"chevron-right\" className={ASIDE_HEADER_BACK_ICON} />\n </IconButton>\n )}\n <h2 className={ASIDE_HEADER_TITLE}>{title}</h2>\n {actions}\n {onClose && (\n <IconButton aria-label={labels.close ?? \"Close\"} size=\"sm\" onClick={onClose}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n );\n}\n\nexport interface AsideRailItem {\n id: string;\n /** Names the button and fills its tooltip. */\n label: string;\n icon: React.ReactNode;\n}\n\nexport interface AsideRailProps extends Omit<React.ComponentProps<\"div\">, \"onChange\"> {\n items: readonly AsideRailItem[];\n /** Pinned to the bottom, e.g. settings. */\n endItems?: readonly AsideRailItem[];\n /** The open aside's id; `null` when all are closed. */\n value: string | null;\n /** Pressing the open item's button asks for `null`. */\n onValueChange: (id: string | null) => void;\n /** Accessible name of the rail. Default `\"Panels\"`. */\n \"aria-label\"?: string;\n}\n\n/** The 44 px strip of icon buttons that opens and closes the asides. */\nexport function AsideRail({\n items,\n endItems = [],\n value,\n onValueChange,\n className,\n \"aria-label\": ariaLabel = \"Panels\",\n ...props\n}: AsideRailProps) {\n const button = (item: AsideRailItem) => (\n <IconButton\n key={item.id}\n aria-label={item.label}\n tooltip={item.label}\n pressed={value === item.id}\n onClick={() => onValueChange(value === item.id ? null : item.id)}\n >\n {item.icon}\n </IconButton>\n );\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n data-slot=\"aside-rail\"\n className={cn(ASIDE_RAIL_BASE, className)}\n {...props}\n >\n {items.map(button)}\n <span className={ASIDE_RAIL_SPACER} />\n {endItems.map(button)}\n </div>\n );\n}\n\nfunction useAtLeast(query: string): boolean {\n return React.useSyncExternalStore(\n (notify) => {\n if (typeof window.matchMedia !== \"function\") return () => {};\n const mq = window.matchMedia(query);\n mq.addEventListener(\"change\", notify);\n return () => mq.removeEventListener(\"change\", notify);\n },\n () => typeof window.matchMedia !== \"function\" || window.matchMedia(query).matches,\n // Server and first paint assume a wide screen; a phone swaps to the drawer on hydrate.\n () => true,\n );\n}\n\nexport type AsidePanelMode = \"docked\" | \"overlay\";\n\nexport interface AsidePanelProps extends Omit<React.ComponentProps<\"aside\">, \"title\"> {\n /** Names the panel. */\n \"aria-label\": string;\n mode?: AsidePanelMode;\n /** Which edge of the screen it hugs. Default `right`. */\n side?: AsidePanelSide;\n open?: boolean;\n /**\n * Escape, the scrim and the drawer drag call it with `false`. A docked panel\n * is only dismissable this way where it falls back to the drawer (below `md`).\n */\n onOpenChange?: (open: boolean) => void;\n /** An `AsideHeader`. */\n header?: React.ReactNode;\n /** Sticky under the body: actions. */\n footer?: React.ReactNode;\n /**\n * Below this width the panel floats on a drawer so the content keeps its room.\n * `false` keeps it docked (or on a sheet) at every width, for a framed preview\n * or an embedded layout where the viewport says nothing about the room.\n */\n collapseBelow?: \"md\" | false;\n /** Docked 380 (260 for a left navigation), overlay 420: a width class overrides it. */\n className?: string;\n}\n\n/**\n * A side panel. `docked` sits in the layout and takes room from the content;\n * `overlay` floats over a scrim on a Sheet. Below `md` both use a bottom Drawer.\n * The edge facing the content is the only border.\n */\nexport function AsidePanel({\n mode = \"docked\",\n side = \"right\",\n open = true,\n onOpenChange,\n header,\n footer,\n collapseBelow = \"md\",\n className,\n children,\n ...props\n}: AsidePanelProps) {\n const viewportWide = useAtLeast(\"(min-width: 768px)\");\n const wide = collapseBelow === false || viewportWide;\n const body = (\n <>\n {header}\n <div data-slot=\"aside-body\" className={ASIDE_BODY}>\n {children}\n </div>\n {footer != null && (\n <div data-slot=\"aside-footer\" className={ASIDE_FOOTER}>\n {footer}\n </div>\n )}\n </>\n );\n\n if (mode === \"docked\" && wide) {\n if (!open) return null;\n return (\n <aside\n data-slot=\"aside-panel\"\n data-mode=\"docked\"\n data-side={side}\n className={cn(ASIDE_PANEL_DOCKED, asidePanelSideClasses[side], className)}\n {...props}\n >\n {body}\n </aside>\n );\n }\n\n // Below md there is no room beside the content, so a docked panel floats like an overlay.\n const label = props[\"aria-label\"];\n if (!wide) {\n return (\n <Drawer open={open} {...(onOpenChange ? { onOpenChange } : {})} direction=\"bottom\">\n <DrawerContent aria-label={label} className={ASIDE_PANEL_DRAWER}>\n {body}\n </DrawerContent>\n </Drawer>\n );\n }\n return (\n <Sheet open={open} {...(onOpenChange ? { onOpenChange } : {})}>\n <SheetContent\n side={side}\n aria-label={label}\n className={cn(ASIDE_PANEL_SHEET, className)}\n >\n {body}\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":"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","names":[]}
@@ -5,7 +5,7 @@ import { Slot } from "../primitives/slot.js";
5
5
  import { BADGE_BASE, badgeVariants } from "../generated/badge.js";
6
6
  function Badge({
7
7
  className,
8
- variant = "primary",
8
+ variant = "neutral",
9
9
  asChild = false,
10
10
  ...props
11
11
  }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/badge.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Slot } from \"../primitives/slot\";\nimport { BADGE_BASE, badgeVariants, type BadgeVariant } from \"../generated/badge\";\n\nexport type { BadgeVariant };\n\nexport interface BadgeProps extends React.ComponentProps<\"span\"> {\n variant?: BadgeVariant;\n asChild?: boolean;\n}\n\nexport function Badge({\n className,\n variant = \"primary\",\n asChild = false,\n ...props\n}: BadgeProps) {\n const Comp = asChild ? Slot : \"span\";\n return (\n <Comp\n data-slot=\"badge\"\n className={cn(BADGE_BASE, badgeVariants[variant], className)}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n"],"mappings":"AAoBI;AApBJ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,YAAY;AACrB,SAAS,YAAY,qBAAwC;AAStD,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,GAAG;AACL,GAAe;AACb,QAAM,OAAO,UAAU,OAAO;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,YAAY,cAAc,OAAO,GAAG,SAAS;AAAA,MAC1D,GAAI;AAAA;AAAA,EACP;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/badge.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Slot } from \"../primitives/slot\";\nimport { BADGE_BASE, badgeVariants, type BadgeVariant } from \"../generated/badge\";\n\nexport type { BadgeVariant };\n\nexport interface BadgeProps extends React.ComponentProps<\"span\"> {\n variant?: BadgeVariant;\n asChild?: boolean;\n}\n\nexport function Badge({\n className,\n variant = \"neutral\",\n asChild = false,\n ...props\n}: BadgeProps) {\n const Comp = asChild ? Slot : \"span\";\n return (\n <Comp\n data-slot=\"badge\"\n className={cn(BADGE_BASE, badgeVariants[variant], className)}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n"],"mappings":"AAoBI;AApBJ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,YAAY;AACrB,SAAS,YAAY,qBAAwC;AAStD,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,GAAG;AACL,GAAe;AACb,QAAM,OAAO,UAAU,OAAO;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,YAAY,cAAc,OAAO,GAAG,SAAS;AAAA,MAC1D,GAAI;AAAA;AAAA,EACP;AAEJ;","names":[]}
@@ -27,27 +27,19 @@ function buttonVariants({
27
27
  function accentClass(accent, variant) {
28
28
  return variant === "outline" ? accentOutlineClasses[accent] : accentFillClasses[accent];
29
29
  }
30
- function Button({
31
- className,
32
- variant = "primary",
33
- size = "md",
34
- icon = false,
35
- accent,
36
- asChild = false,
37
- href,
38
- ...props
39
- }) {
30
+ const Button = React.forwardRef(function Button2({ className, variant = "primary", size = "md", icon = false, accent, asChild = false, href, ...props }, ref) {
40
31
  const Comp = asChild ? Slot : href !== void 0 ? "a" : "button";
41
32
  return /* @__PURE__ */ jsx(
42
33
  Comp,
43
34
  {
44
35
  "data-slot": "button",
45
36
  href,
37
+ ref,
46
38
  className: cn(buttonVariants({ variant, size, icon, accent }), className),
47
39
  ...props
48
40
  }
49
41
  );
50
- }
42
+ });
51
43
  export {
52
44
  Button,
53
45
  buttonVariants