@tea-ui/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/card.d.ts +60 -0
- package/dist/card.js +116 -0
- package/dist/card.js.map +1 -0
- package/dist/feedback/alert.d.ts +57 -0
- package/dist/feedback/alert.js +104 -0
- package/dist/feedback/alert.js.map +1 -0
- package/dist/feedback/badge.d.ts +34 -0
- package/dist/feedback/badge.js +62 -0
- package/dist/feedback/badge.js.map +1 -0
- package/dist/feedback/index.d.ts +13 -0
- package/dist/feedback/index.js +62 -0
- package/dist/feedback/index.js.map +1 -0
- package/dist/feedback/progress.d.ts +25 -0
- package/dist/feedback/progress.js +49 -0
- package/dist/feedback/progress.js.map +1 -0
- package/dist/feedback/skeleton.d.ts +38 -0
- package/dist/feedback/skeleton.js +49 -0
- package/dist/feedback/skeleton.js.map +1 -0
- package/dist/feedback/spinner.d.ts +31 -0
- package/dist/feedback/spinner.js +55 -0
- package/dist/feedback/spinner.js.map +1 -0
- package/dist/feedback/status.d.ts +63 -0
- package/dist/feedback/status.js +156 -0
- package/dist/feedback/status.js.map +1 -0
- package/dist/feedback/toast.d.ts +72 -0
- package/dist/feedback/toast.js +211 -0
- package/dist/feedback/toast.js.map +1 -0
- package/dist/format/index.d.ts +63 -0
- package/dist/format/index.js +84 -0
- package/dist/format/index.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -0
- package/dist/inputs/button-group.d.ts +70 -0
- package/dist/inputs/button-group.js +59 -0
- package/dist/inputs/button-group.js.map +1 -0
- package/dist/inputs/button.d.ts +52 -0
- package/dist/inputs/button.js +84 -0
- package/dist/inputs/button.js.map +1 -0
- package/dist/inputs/checkbox.d.ts +48 -0
- package/dist/inputs/checkbox.js +74 -0
- package/dist/inputs/checkbox.js.map +1 -0
- package/dist/inputs/combobox.d.ts +77 -0
- package/dist/inputs/combobox.js +273 -0
- package/dist/inputs/combobox.js.map +1 -0
- package/dist/inputs/field.d.ts +220 -0
- package/dist/inputs/field.js +183 -0
- package/dist/inputs/field.js.map +1 -0
- package/dist/inputs/icon-button.d.ts +44 -0
- package/dist/inputs/icon-button.js +30 -0
- package/dist/inputs/icon-button.js.map +1 -0
- package/dist/inputs/index.d.ts +22 -0
- package/dist/inputs/index.js +92 -0
- package/dist/inputs/index.js.map +1 -0
- package/dist/inputs/input-group.d.ts +77 -0
- package/dist/inputs/input-group.js +76 -0
- package/dist/inputs/input-group.js.map +1 -0
- package/dist/inputs/input.d.ts +58 -0
- package/dist/inputs/input.js +68 -0
- package/dist/inputs/input.js.map +1 -0
- package/dist/inputs/label.d.ts +47 -0
- package/dist/inputs/label.js +29 -0
- package/dist/inputs/label.js.map +1 -0
- package/dist/inputs/number-input.d.ts +62 -0
- package/dist/inputs/number-input.js +86 -0
- package/dist/inputs/number-input.js.map +1 -0
- package/dist/inputs/password-input.d.ts +66 -0
- package/dist/inputs/password-input.js +73 -0
- package/dist/inputs/password-input.js.map +1 -0
- package/dist/inputs/radio.d.ts +66 -0
- package/dist/inputs/radio.js +96 -0
- package/dist/inputs/radio.js.map +1 -0
- package/dist/inputs/search-input.d.ts +66 -0
- package/dist/inputs/search-input.js +76 -0
- package/dist/inputs/search-input.js.map +1 -0
- package/dist/inputs/select.d.ts +65 -0
- package/dist/inputs/select.js +139 -0
- package/dist/inputs/select.js.map +1 -0
- package/dist/inputs/slider.d.ts +67 -0
- package/dist/inputs/slider.js +107 -0
- package/dist/inputs/slider.js.map +1 -0
- package/dist/inputs/switch.d.ts +42 -0
- package/dist/inputs/switch.js +69 -0
- package/dist/inputs/switch.js.map +1 -0
- package/dist/inputs/textarea.d.ts +51 -0
- package/dist/inputs/textarea.js +82 -0
- package/dist/inputs/textarea.js.map +1 -0
- package/dist/inputs/toggle.d.ts +57 -0
- package/dist/inputs/toggle.js +91 -0
- package/dist/inputs/toggle.js.map +1 -0
- package/dist/internal/index.d.ts +5 -0
- package/dist/internal/index.js +19 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/internal/refs.d.ts +31 -0
- package/dist/internal/refs.js +34 -0
- package/dist/internal/refs.js.map +1 -0
- package/dist/internal/slot.d.ts +76 -0
- package/dist/internal/slot.js +40 -0
- package/dist/internal/slot.js.map +1 -0
- package/dist/internal/use-controllable-state.d.ts +25 -0
- package/dist/internal/use-controllable-state.js +38 -0
- package/dist/internal/use-controllable-state.js.map +1 -0
- package/dist/layout/index.d.ts +131 -0
- package/dist/layout/index.js +212 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/navigation/index.d.ts +114 -0
- package/dist/navigation/index.js +523 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/overlays/alert-dialog.d.ts +75 -0
- package/dist/overlays/alert-dialog.js +163 -0
- package/dist/overlays/alert-dialog.js.map +1 -0
- package/dist/overlays/dialog.d.ts +44 -0
- package/dist/overlays/dialog.js +121 -0
- package/dist/overlays/dialog.js.map +1 -0
- package/dist/overlays/drawer.d.ts +49 -0
- package/dist/overlays/drawer.js +125 -0
- package/dist/overlays/drawer.js.map +1 -0
- package/dist/overlays/dropdown-menu.d.ts +53 -0
- package/dist/overlays/dropdown-menu.js +192 -0
- package/dist/overlays/dropdown-menu.js.map +1 -0
- package/dist/overlays/index.d.ts +10 -0
- package/dist/overlays/index.js +169 -0
- package/dist/overlays/index.js.map +1 -0
- package/dist/overlays/popover.d.ts +58 -0
- package/dist/overlays/popover.js +105 -0
- package/dist/overlays/popover.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/typography/index.d.ts +79 -0
- package/dist/typography/index.js +280 -0
- package/dist/typography/index.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/navigation/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Slot } from \"radix-ui\";\nimport {\n Accordion as AccordionPrimitive,\n Collapsible as CollapsiblePrimitive,\n NavigationMenu as NavigationMenuPrimitive,\n Tabs as TabsPrimitive,\n Toolbar as ToolbarPrimitive,\n} from \"radix-ui\";\nimport { ChevronDown, ChevronsLeft, ChevronsRight } from \"@tea-ui/icons\";\nimport { cn, cva } from \"@tea-ui/utils\";\nimport { COPY } from \"@tea-ui/ux-standards\";\n\nimport { dataSlot, useControllableState } from \"../internal\";\nimport { Button } from \"../inputs/button\";\n\n/* ========================================================================== */\n/* Tabs */\n/* ========================================================================== */\n\n/**\n * Tabs are for **peer views of the same subject**. If the things being switched\n * between are not peers, they are navigation, and they belong in a sidebar —\n * promoting peers into the top level is how information architecture collapses.\n *\n * The active indicator is a weight change as well as a colour change. A tab\n * that is only distinguished by hue is unreadable to a user with a colour\n * vision deficiency and invisible in a screenshot printed in greyscale.\n */\nexport interface TabsProps\n extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>, \"children\"> {\n children: React.ReactNode;\n}\n\nexport const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(function Tabs({ children, ...props }, ref) {\n return <TabsPrimitive.Root ref={ref} {...props} {...dataSlot(\"tabs\")}>{children}</TabsPrimitive.Root>;\n});\n\nexport const TabsList = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(function TabsList({ className, ...props }, ref) {\n return (\n <TabsPrimitive.List\n ref={ref}\n className={cn(\n \"inline-flex items-center gap-0 border-b border-line\",\n \"data-[orientation=vertical]:flex-col data-[orientation=vertical]:border-b-0 data-[orientation=vertical]:border-e\",\n className,\n )}\n {...dataSlot(\"tabs\", \"list\")}\n {...props}\n />\n );\n});\n\nexport const TabsTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(function TabsTrigger({ className, ...props }, ref) {\n return (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"relative control-h border-0 bg-transparent px-3 text-ui text-fg-muted\",\n \"border-b-2 border-transparent transition-colors duration-fast ease-standard\",\n \"hover:text-fg\",\n \"data-[state=active]:border-primary data-[state=active]:text-primary data-[state=active]:font-medium\",\n \"data-[orientation=vertical]:w-full data-[orientation=vertical]:border-e-0 data-[orientation=vertical]:border-s-2 data-[orientation=vertical]:text-start\",\n className,\n )}\n data-tea-touch\n {...dataSlot(\"tabs\", \"trigger\")}\n {...props}\n />\n );\n});\n\nexport const TabsContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(function TabsContent({ className, ...props }, ref) {\n return (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\"focus-visible:outline-none\", className)}\n tabIndex={0}\n {...dataSlot(\"tabs\", \"content\")}\n {...props}\n />\n );\n});\n\n/* ========================================================================== */\n/* Collapsible */\n/* ========================================================================== */\n\nexport interface CollapsibleProps\n extends Omit<React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Root>, \"defaultValue\"> {\n defaultOpen?: boolean | undefined;\n}\n\n/** Progressive disclosure. The trigger owns `aria-expanded` and `aria-controls`. */\nexport const Collapsible = React.forwardRef<HTMLDivElement, CollapsibleProps>(function Collapsible(\n { defaultOpen = false, ...props },\n ref,\n) {\n return <CollapsiblePrimitive.Root ref={ref} defaultOpen={defaultOpen} {...dataSlot(\"collapsible\")} {...props} />;\n});\n\nexport const CollapsibleTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleTrigger>\n>(function CollapsibleTrigger({ className, children, ...props }, ref) {\n return (\n <CollapsiblePrimitive.CollapsibleTrigger\n ref={ref}\n className={cn(\n \"group flex w-full items-center justify-between gap-2 text-start text-ui font-medium text-fg\",\n className,\n )}\n data-tea-touch\n {...dataSlot(\"collapsible\", \"trigger\")}\n {...props}\n >\n {children}\n <ChevronDown\n size={16}\n aria-hidden=\"true\"\n className=\"shrink-0 text-fg-muted transition-transform duration-fast group-data-[state=open]:rotate-180\"\n />\n </CollapsiblePrimitive.CollapsibleTrigger>\n );\n});\n\nexport const CollapsibleContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleContent>\n>(function CollapsibleContent({ className, ...props }, ref) {\n return (\n <CollapsiblePrimitive.CollapsibleContent\n ref={ref}\n className={cn(\n \"overflow-hidden\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-top\",\n className,\n )}\n {...dataSlot(\"collapsible\", \"content\")}\n {...props}\n />\n );\n});\n\n/* ========================================================================== */\n/* Accordion */\n/* ========================================================================== */\n\nexport interface AccordionProps\n extends Omit<React.ComponentProps<\"div\">, \"children\" | \"color\" | \"dir\"> {\n type?: \"single\" | \"multiple\" | undefined;\n /** Single type only: allow closing the open item. */\n collapsible?: boolean | undefined;\n defaultValue?: string | string[] | undefined;\n value?: string | string[] | undefined;\n onValueChange?: ((value: never) => void) | undefined;\n children: React.ReactNode;\n}\n\n/**\n * Radix types the accordion root as a `single | multiple` union, so a forwardable\n * `type` and `value` pair is genuinely correct and only the narrowing is\n * unrepresentable. Dispatched once, here.\n */\nconst AccordionRootPrimitive = AccordionPrimitive.Root as unknown as React.ComponentType<\n React.ComponentProps<\"div\"> & Record<string, unknown>\n>;\n\n/**\n * An accordion is a list of questions with answers that belong together. If the\n * sections are unrelated, they are `<Collapsible>`s, not one accordion — a\n * single-value accordion forces the user to close one answer to read the next,\n * which only makes sense when the answers are alternatives.\n */\nexport const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(function Accordion(\n { type = \"single\", collapsible = true, defaultValue, value, onValueChange, className, children, ...props },\n ref,\n) {\n return (\n <AccordionRootPrimitive\n ref={ref}\n type={type}\n collapsible={type === \"single\" ? collapsible : undefined}\n defaultValue={defaultValue}\n value={value}\n onValueChange={onValueChange}\n className={cn(\"border-t border-line\", className)}\n {...dataSlot(\"accordion\")}\n {...props}\n >\n {children}\n </AccordionRootPrimitive>\n );\n});\n\nexport const AccordionItem = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(function AccordionItem({ className, ...props }, ref) {\n return (\n <AccordionPrimitive.Item ref={ref} className={cn(\"border-b border-line\", className)} {...dataSlot(\"accordion\", \"item\")} {...props} />\n );\n});\n\nexport const AccordionTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(function AccordionTrigger({ className, children, ...props }, ref) {\n return (\n <AccordionPrimitive.Header className=\"flex\">\n <AccordionPrimitive.Trigger\n ref={ref}\n className={cn(\n \"group flex flex-1 items-center justify-between gap-3 py-3 text-start text-ui font-medium text-fg\",\n className,\n )}\n data-tea-touch\n {...dataSlot(\"accordion\", \"trigger\")}\n {...props}\n >\n {children}\n <ChevronDown\n size={16}\n aria-hidden=\"true\"\n className=\"shrink-0 text-fg-muted transition-transform duration-fast group-data-[state=open]:rotate-180\"\n />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n );\n});\n\nexport const AccordionContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(function AccordionContent({ className, children, ...props }, ref) {\n return (\n <AccordionPrimitive.Content\n ref={ref}\n className={cn(\n \"overflow-hidden text-ui text-fg-muted\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-top\",\n className,\n )}\n {...dataSlot(\"accordion\", \"content\")}\n {...props}\n >\n <div className=\"pb-3 pe-8\">{children}</div>\n </AccordionPrimitive.Content>\n );\n});\n\n/* ========================================================================== */\n/* Breadcrumb */\n/* ========================================================================== */\n\n/**\n * A breadcrumb shows *where you are*; a back button shows *where you were*.\n * Below three levels of hierarchy you need both, and neither alone.\n */\nexport const Breadcrumb = React.forwardRef<HTMLElement, React.ComponentProps<\"nav\">>(function Breadcrumb(\n { className, children, ...props },\n ref,\n) {\n return (\n <nav ref={ref} aria-label={COPY.navigation.breadcrumb} className={cn(className)} {...dataSlot(\"breadcrumb\")} {...props}>\n <ol className=\"flex flex-wrap items-center gap-1 text-micro text-fg-muted\">{children}</ol>\n </nav>\n );\n});\n\nexport const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n function BreadcrumbItem({ className, ...props }, ref) {\n return <li ref={ref} className={cn(\"flex items-center gap-1\", className)} {...dataSlot(\"breadcrumb\", \"item\")} {...props} />;\n },\n);\n\nexport interface BreadcrumbLinkProps extends React.ComponentProps<\"a\"> {\n asChild?: boolean | undefined;\n}\n\nexport const BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(\n function BreadcrumbLink({ className, asChild = false, children, ...props }, ref) {\n const Component = asChild ? Slot.Root : \"a\";\n return (\n <Component\n ref={ref}\n className={cn(\"transition-colors hover:text-fg\", className)}\n {...dataSlot(\"breadcrumb\", \"link\")}\n {...props}\n >\n {children}\n </Component>\n );\n },\n);\n\n/** The current page. `aria-current` is what makes it announceable, not the style. */\nexport const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentProps<\"span\">>(\n function BreadcrumbPage({ className, ...props }, ref) {\n return (\n <span\n ref={ref}\n aria-current=\"page\"\n className={cn(\"font-medium text-fg\", className)}\n {...dataSlot(\"breadcrumb\", \"page\")}\n {...props}\n />\n );\n },\n);\n\n/** Decorative by definition — the `<ol>` already conveys the hierarchy. */\nexport const BreadcrumbSeparator = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n function BreadcrumbSeparator({ className, children, ...props }, ref) {\n return (\n <li ref={ref} aria-hidden=\"true\" className={cn(\"text-fg-subtle\", className)} {...dataSlot(\"breadcrumb\", \"separator\")} {...props}>\n {children ?? \"/\"}\n </li>\n );\n },\n);\n\n/* ========================================================================== */\n/* Pagination */\n/* ========================================================================== */\n\nexport const paginationLinkVariants = cva(\n \"inline-flex min-w-8 items-center justify-center gap-1 border border-line px-2 text-ui text-fg-muted transition-colors hover:border-line-strong hover:text-fg aria-[current=page]:border-primary aria-[current=page]:text-primary\",\n);\n\nexport interface PaginationProps extends Omit<React.ComponentProps<\"nav\">, \"onChange\"> {\n /** Current page, 1-based. */\n page: number;\n /** How many pages exist in total. */\n pageCount: number;\n onPageChange: (page: number) => void;\n /** Optional element describing what is being paginated. */\n itemLabel?: string | undefined;\n className?: string | undefined;\n}\n\nexport function Pagination({\n page,\n pageCount,\n onPageChange,\n itemLabel,\n className,\n ...props\n}: PaginationProps): React.ReactElement {\n const go = (next: number) => onPageChange(Math.min(pageCount, Math.max(1, next)));\n return (\n <nav\n aria-label={COPY.a11y.page}\n className={cn(\"flex flex-wrap items-center justify-between gap-3\", className)}\n {...dataSlot(\"pagination\")}\n {...props}\n >\n <PaginationSummary page={page} pageCount={pageCount} itemLabel={itemLabel} />\n <ul className=\"flex items-center gap-1\">\n <li>\n <PaginationPrevious page={page} onPageChange={go} />\n </li>\n {pageWindow(page, pageCount).map((entry, index) =>\n entry === \"ellipsis\" ? (\n <li key={`gap-${index}`} aria-hidden=\"true\" className=\"px-1 text-fg-subtle\">\n …\n </li>\n ) : (\n <li key={entry}>\n <PaginationLink page={entry} onPageChange={go} />\n </li>\n ),\n )}\n <li>\n <PaginationNext page={page} pageCount={pageCount} onPageChange={go} />\n </li>\n </ul>\n </nav>\n );\n}\n\nfunction PaginationSummary({\n page,\n pageCount,\n itemLabel,\n}: {\n page: number;\n pageCount: number;\n itemLabel?: string | undefined;\n}): React.ReactElement {\n return (\n <p className=\"text-micro text-fg-muted\">\n {COPY.a11y.page} {page} {COPY.a11y.of} {pageCount}\n {itemLabel ? ` · ${itemLabel}` : null}\n </p>\n );\n}\n\nfunction PaginationLink({ page, onPageChange }: { page: number; onPageChange: (page: number) => void }): React.ReactElement {\n return (\n <button\n type=\"button\"\n onClick={() => onPageChange(page)}\n aria-current=\"page\"\n aria-label={`${COPY.a11y.page} ${page}`}\n className={paginationLinkVariants()}\n data-tea-touch\n >\n {page}\n </button>\n );\n}\n\nfunction PaginationPrevious({\n page,\n onPageChange,\n}: {\n page: number;\n onPageChange: (page: number) => void;\n}): React.ReactElement {\n return (\n <button\n type=\"button\"\n onClick={() => onPageChange(page - 1)}\n disabled={page <= 1}\n aria-label={COPY.actions.back}\n className={cn(paginationLinkVariants(), \"disabled:pointer-events-none disabled:opacity-50\")}\n data-tea-touch\n >\n <ChevronsLeft size={14} aria-hidden=\"true\" />\n </button>\n );\n}\n\nfunction PaginationNext({\n page,\n pageCount,\n onPageChange,\n}: {\n page: number;\n pageCount: number;\n onPageChange: (page: number) => void;\n}): React.ReactElement {\n return (\n <button\n type=\"button\"\n onClick={() => onPageChange(page + 1)}\n disabled={page >= pageCount}\n aria-label={COPY.actions.next}\n className={cn(paginationLinkVariants(), \"disabled:pointer-events-none disabled:opacity-50\")}\n data-tea-touch\n >\n <ChevronsRight size={14} aria-hidden=\"true\" />\n </button>\n );\n}\n\n/** A short window around the current page; never more than nine entries. */\nfunction pageWindow(page: number, pageCount: number): Array<number | \"ellipsis\"> {\n if (pageCount <= 7) return Array.from({ length: pageCount }, (_, index) => index + 1);\n const entries: Array<number | \"ellipsis\"> = [1];\n const start = Math.max(2, page - 1);\n const end = Math.min(pageCount - 1, page + 1);\n if (start > 2) entries.push(\"ellipsis\");\n for (let index = start; index <= end; index += 1) entries.push(index);\n if (end < pageCount - 1) entries.push(\"ellipsis\");\n entries.push(pageCount);\n return entries;\n}\n\n/* ========================================================================== */\n/* Skip link */\n/* ========================================================================== */\n\n/**\n * The first focusable element on every page. Neither source product had one.\n *\n * It is visually hidden until focused, and it is genuinely useful rather than\n * ceremonial: a keyboard user arriving on a page with a 20-item sidebar would\n * otherwise have to tab through all of it on every single page.\n */\nexport interface SkipLinkProps extends React.ComponentProps<\"a\"> {\n /** The id of the main region to jump to. */\n targetId: string;\n}\n\nexport function SkipLink({ targetId, className, children, ...props }: SkipLinkProps): React.ReactElement {\n return (\n <a\n href={`#${targetId}`}\n className={cn(\n \"sr-only z-max focus:not-sr-only\",\n \"focus:fixed focus:start-3 focus:top-3 focus:border focus:border-primary focus:bg-primary focus:px-3 focus:py-2 focus:text-ui focus:font-medium focus:text-primary-fg\",\n className,\n )}\n {...dataSlot(\"skip-link\")}\n {...props}\n >\n {children ?? COPY.navigation.skipToContent}\n </a>\n );\n}\n\n/* ========================================================================== */\n/* Stepper */\n/* ========================================================================== */\n\nexport interface StepperProps extends Omit<React.ComponentProps<\"ol\">, \"children\"> {\n /** Zero-based index of the current step. */\n current: number;\n children: React.ReactNode;\n}\n\nexport const Stepper = React.forwardRef<HTMLOListElement, StepperProps>(function Stepper(\n { current, children, className, ...props },\n ref,\n) {\n return (\n <ol\n ref={ref}\n className={cn(\"flex w-full items-center gap-2\", className)}\n data-current-step={current}\n {...dataSlot(\"stepper\")}\n {...props}\n >\n {children}\n </ol>\n );\n});\n\nexport interface StepProps extends React.ComponentProps<\"li\"> {\n /** Index of this step. */\n index: number;\n /** Index of the current step. */\n current: number;\n /** Show a state label such as \"Abgeschlossen\". */\n stateLabel?: string | undefined;\n}\n\nexport function Step({ index, current, stateLabel, className, children, ...props }: StepProps): React.ReactElement {\n const state = index < current ? \"complete\" : index === current ? \"current\" : \"upcoming\";\n return (\n <li\n aria-current={state === \"current\" ? \"step\" : undefined}\n data-state={state}\n className={cn(\"flex min-w-0 flex-1 items-center gap-2\", className)}\n {...dataSlot(\"stepper\", \"step\")}\n {...props}\n >\n {children}\n {stateLabel ? <span className=\"sr-only\">{stateLabel}</span> : null}\n </li>\n );\n}\n\nexport function StepIndicator({ className, ...props }: React.ComponentProps<\"span\">): React.ReactElement {\n return (\n <span\n aria-hidden=\"true\"\n className={cn(\n \"flex size-6 shrink-0 items-center justify-center border border-line text-micro font-semibold text-fg-muted\",\n \"group-data-[state=complete]:border-positive group-data-[state=complete]:bg-positive-subtle group-data-[state=complete]:text-positive\",\n \"group-data-[state=current]:border-primary group-data-[state=current]:text-primary\",\n className,\n )}\n {...dataSlot(\"stepper\", \"indicator\")}\n {...props}\n />\n );\n}\n\nexport function StepSeparator({ className, ...props }: React.ComponentProps<\"span\">): React.ReactElement {\n return <span aria-hidden=\"true\" className={cn(\"h-px flex-1 bg-line\", className)} {...dataSlot(\"stepper\", \"separator\")} {...props} />;\n}\n\n/* ========================================================================== */\n/* Toolbar */\n/* ========================================================================== */\n\nexport const Toolbar = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root>\n>(function Toolbar({ className, ...props }, ref) {\n return (\n <ToolbarPrimitive.Root\n ref={ref}\n className={cn(\"flex items-center gap-2 border-b border-line px-3 py-2\", className)}\n data-tea-touch\n {...dataSlot(\"toolbar\")}\n {...props}\n />\n );\n});\n\nexport const ToolbarButton = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n function ToolbarButton({ className, type = \"button\", ...props }, ref) {\n return <Button ref={ref} type={type} variant=\"ghost\" size=\"sm\" className={className} {...props} />;\n },\n);\n\n/* ========================================================================== */\n/* NavigationMenu (mega menu) */\n/* ========================================================================== */\n\nexport const NavigationMenu = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(function NavigationMenu({ className, children, ...props }, ref) {\n return (\n <NavigationMenuPrimitive.Root\n ref={ref}\n className={cn(\"relative flex items-center\", className)}\n {...dataSlot(\"navigation-menu\")}\n {...props}\n >\n {children}\n <NavigationMenuViewport />\n </NavigationMenuPrimitive.Root>\n );\n});\n\nexport const NavigationMenuList = React.forwardRef<\n HTMLUListElement,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(function NavigationMenuList({ className, ...props }, ref) {\n return (\n <NavigationMenuPrimitive.List\n ref={ref}\n className={cn(\"flex items-center gap-1\", className)}\n {...dataSlot(\"navigation-menu\", \"list\")}\n {...props}\n />\n );\n});\n\nexport const NavigationMenuItem = NavigationMenuPrimitive.Item;\n\nexport const NavigationMenuTrigger = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(function NavigationMenuTrigger({ className, children, ...props }, ref) {\n return (\n <NavigationMenuPrimitive.Trigger\n ref={ref}\n className={cn(\"group control-h px-3 text-ui text-fg-muted hover:text-fg\", className)}\n data-tea-touch\n {...dataSlot(\"navigation-menu\", \"trigger\")}\n {...props}\n >\n {children}\n <ChevronDown size={14} aria-hidden=\"true\" className=\"ms-1 transition-transform group-data-[state=open]:rotate-180\" />\n </NavigationMenuPrimitive.Trigger>\n );\n});\n\nexport const NavigationMenuContent = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(function NavigationMenuContent({ className, ...props }, ref) {\n return (\n <NavigationMenuPrimitive.Content\n ref={ref}\n className={cn(\"p-2\", className)}\n {...dataSlot(\"navigation-menu\", \"content\")}\n {...props}\n />\n );\n});\n\nexport const NavigationMenuLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Link> & { asChild?: boolean | undefined }\n>(function NavigationMenuLink({ className, asChild = false, ...props }, ref) {\n return (\n <NavigationMenuPrimitive.Link\n ref={ref}\n asChild={asChild}\n className={cn(\n \"block select-none rounded-none p-2 text-ui text-fg no-underline outline-none transition-colors\",\n \"data-[active]:bg-accent-subtle data-[active]:text-fg\",\n \"focus-visible:bg-accent-subtle\",\n className,\n )}\n {...dataSlot(\"navigation-menu\", \"link\")}\n {...props}\n />\n );\n});\n\nfunction NavigationMenuViewport(): React.ReactElement {\n return (\n <NavigationMenuPrimitive.Viewport\n className={cn(\n \"absolute start-0 top-full z-dropdown mt-1 w-[min(40rem,90vw)] origin-top border border-line bg-surface-2 p-2 shadow-overlay\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n )}\n {...dataSlot(\"navigation-menu\", \"viewport\")}\n />\n );\n}\n\n/** A controlled-state helper exposed for products building their own nav. */\nexport { useControllableState };\n"],"mappings":"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","names":["Tabs","TabsList","TabsTrigger","TabsContent","Collapsible","CollapsibleTrigger","CollapsibleContent","Accordion","AccordionItem","AccordionTrigger","AccordionContent","Breadcrumb","BreadcrumbItem","BreadcrumbLink","BreadcrumbPage","BreadcrumbSeparator","Stepper","Toolbar","ToolbarButton","NavigationMenu","NavigationMenuList","NavigationMenuTrigger","NavigationMenuContent","NavigationMenuLink"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { AlertDialog as AlertDialog$1 } from 'radix-ui';
|
|
3
|
+
import { ConsequenceLevel } from '@tea-ui/ux-standards';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TEA UI — AlertDialog, ConfirmDialog, `useConfirm`.
|
|
7
|
+
*
|
|
8
|
+
* The destructive-action standard, and the fix for an inversion the audit found
|
|
9
|
+
* in both source products: a *reversible* two-step inline confirmation sat
|
|
10
|
+
* beside an *irreversible* `DELETE` that had no confirmation at all. Protection
|
|
11
|
+
* was being spent in the wrong place.
|
|
12
|
+
*
|
|
13
|
+
* Two rules make this correct, and both are enforced here rather than left to
|
|
14
|
+
* the caller:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Cancel is the default focus, and the destructive button is never the
|
|
17
|
+
* primary.** The default variant of the cancel button is `secondary`, the
|
|
18
|
+
* default variant of the confirm button is `outline` for recoverable work
|
|
19
|
+
* and `destructive` only for irreversible work — and it still sits second in
|
|
20
|
+
* the DOM order. A user who presses Enter reflexively must not destroy
|
|
21
|
+
* anything.
|
|
22
|
+
* 2. **The consequence is stated, not implied.** `consequenceSentence()` builds
|
|
23
|
+
* it, so a dialog that says "wird gelöscht" can never sit next to a button
|
|
24
|
+
* that says "Wiederherstellen".
|
|
25
|
+
*
|
|
26
|
+
* For `irreversible`, `confirmWord` must be typed before the action enables.
|
|
27
|
+
*/
|
|
28
|
+
declare const AlertDialog: React.FC<AlertDialog$1.AlertDialogProps>;
|
|
29
|
+
declare const AlertDialogTrigger: React.ForwardRefExoticComponent<AlertDialog$1.AlertDialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
30
|
+
declare const AlertDialogPortal: React.FC<AlertDialog$1.AlertDialogPortalProps>;
|
|
31
|
+
declare const AlertDialogOverlay: React.ForwardRefExoticComponent<Omit<AlertDialog$1.AlertDialogOverlayProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
interface ConfirmDialogProps {
|
|
33
|
+
open: boolean;
|
|
34
|
+
onOpenChange: (open: boolean) => void;
|
|
35
|
+
/** How bad it is. Determines the protection. */
|
|
36
|
+
level: ConsequenceLevel;
|
|
37
|
+
/** The noun phrase, e.g. "Der Server" or "Alle Backups". */
|
|
38
|
+
what: string;
|
|
39
|
+
title?: string | undefined;
|
|
40
|
+
/** Extra detail beyond the generated consequence sentence. */
|
|
41
|
+
description?: string | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Required for `irreversible`. The user must type this exactly before the
|
|
44
|
+
* action enables — the last check for an action with no undo.
|
|
45
|
+
*/
|
|
46
|
+
confirmWord?: string | undefined;
|
|
47
|
+
confirmLabel?: string | undefined;
|
|
48
|
+
cancelLabel?: string | undefined;
|
|
49
|
+
/** Run when the user confirms. May be async; the dialog waits for it. */
|
|
50
|
+
onConfirm: () => void | Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
declare function ConfirmDialog({ open, onOpenChange, level, what, title, description, confirmWord, confirmLabel, cancelLabel, onConfirm, }: ConfirmDialogProps): React.ReactElement;
|
|
53
|
+
interface ConfirmOptions extends Omit<ConfirmDialogProps, "open" | "onOpenChange" | "onConfirm"> {
|
|
54
|
+
onConfirm?: () => void | Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Promise-based confirmation.
|
|
59
|
+
*
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const [confirm, ask] = useConfirm();
|
|
62
|
+
* const onDelete = async () => {
|
|
63
|
+
* const ok = await ask({ level: "irreversible", what: "Der Server", confirmWord: "srv-1" });
|
|
64
|
+
* if (ok) await remove();
|
|
65
|
+
* };
|
|
66
|
+
* return <>{confirm}<Button onClick={onDelete}>Löschen</Button></>;
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* The pending promise is tracked in a ref and resolved in an effect, so an
|
|
70
|
+
* unmount while a dialog is open resolves to `false` instead of leaving the
|
|
71
|
+
* caller's `await` hanging forever.
|
|
72
|
+
*/
|
|
73
|
+
declare function useConfirm(): [React.ReactNode, ConfirmFn];
|
|
74
|
+
|
|
75
|
+
export { AlertDialog, AlertDialogOverlay, AlertDialogPortal, AlertDialogTrigger, ConfirmDialog, type ConfirmDialogProps, type ConfirmFn, type ConfirmOptions, useConfirm };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
4
|
+
import { OctagonAlert } from "@tea-ui/icons";
|
|
5
|
+
import { cn } from "@tea-ui/utils";
|
|
6
|
+
import {
|
|
7
|
+
COPY,
|
|
8
|
+
DESTRUCTIVE_VERBS,
|
|
9
|
+
consequenceSentence
|
|
10
|
+
} from "@tea-ui/ux-standards";
|
|
11
|
+
import { dataSlot } from "../internal/index.js";
|
|
12
|
+
import { Button } from "../inputs/button.js";
|
|
13
|
+
import { Input } from "../inputs/input.js";
|
|
14
|
+
import { Field, FieldLabel } from "../inputs/field.js";
|
|
15
|
+
const AlertDialog = AlertDialogPrimitive.Root;
|
|
16
|
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
|
17
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
18
|
+
const AlertDialogOverlay = React.forwardRef(function AlertDialogOverlay2({ className, ...props }, ref) {
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
AlertDialogPrimitive.Overlay,
|
|
21
|
+
{
|
|
22
|
+
ref,
|
|
23
|
+
className: cn(
|
|
24
|
+
"fixed inset-0 z-modal bg-overlay data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
25
|
+
className
|
|
26
|
+
),
|
|
27
|
+
...dataSlot("alert-dialog", "overlay"),
|
|
28
|
+
...props
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
function ConfirmDialog({
|
|
33
|
+
open,
|
|
34
|
+
onOpenChange,
|
|
35
|
+
level,
|
|
36
|
+
what,
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
confirmWord,
|
|
40
|
+
confirmLabel,
|
|
41
|
+
cancelLabel,
|
|
42
|
+
onConfirm
|
|
43
|
+
}) {
|
|
44
|
+
const [typed, setTyped] = React.useState("");
|
|
45
|
+
const [busy, setBusy] = React.useState(false);
|
|
46
|
+
if (!open && typed !== "") setTyped("");
|
|
47
|
+
const needsWord = level === "irreversible" && Boolean(confirmWord);
|
|
48
|
+
const canConfirm = !needsWord || typed.trim() === confirmWord;
|
|
49
|
+
return /* @__PURE__ */ jsx(AlertDialogPrimitive.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs(AlertDialogPrimitive.Portal, { children: [
|
|
50
|
+
/* @__PURE__ */ jsx(AlertDialogOverlay, {}),
|
|
51
|
+
/* @__PURE__ */ jsxs(
|
|
52
|
+
AlertDialogPrimitive.Content,
|
|
53
|
+
{
|
|
54
|
+
className: cn(
|
|
55
|
+
"fixed start-1/2 top-1/2 z-modal w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2",
|
|
56
|
+
"border border-critical-border bg-surface p-4 shadow-modal",
|
|
57
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
58
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
|
|
59
|
+
),
|
|
60
|
+
...dataSlot("confirm-dialog", "content"),
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(OctagonAlert, { size: 20, "aria-hidden": "true", className: "mt-0.5 shrink-0 text-critical" }),
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
65
|
+
/* @__PURE__ */ jsx(AlertDialogPrimitive.Title, { className: "text-title font-semibold text-fg", children: title ?? COPY.destructive.confirmTitle }),
|
|
66
|
+
/* @__PURE__ */ jsxs(AlertDialogPrimitive.Description, { className: "mt-1 text-ui text-fg-muted", children: [
|
|
67
|
+
consequenceSentence(what, level),
|
|
68
|
+
description ? ` ${description}` : null
|
|
69
|
+
] })
|
|
70
|
+
] })
|
|
71
|
+
] }),
|
|
72
|
+
needsWord ? /* @__PURE__ */ jsxs(Field, { className: "mt-4", id: "tea-confirm-word", children: [
|
|
73
|
+
/* @__PURE__ */ jsx(FieldLabel, { children: COPY.destructive.typeToConfirm.replace("{name}", confirmWord ?? "") }),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
Input,
|
|
76
|
+
{
|
|
77
|
+
value: typed,
|
|
78
|
+
autoComplete: "off",
|
|
79
|
+
onChange: (event) => setTyped(event.target.value),
|
|
80
|
+
"aria-invalid": typed.length > 0 && typed.trim() !== confirmWord ? true : void 0
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
] }) : null,
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [
|
|
85
|
+
/* @__PURE__ */ jsx(AlertDialogPrimitive.Cancel, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", disabled: busy, children: cancelLabel ?? DESTRUCTIVE_VERBS.cancel }) }),
|
|
86
|
+
/* @__PURE__ */ jsx(AlertDialogPrimitive.Action, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
87
|
+
Button,
|
|
88
|
+
{
|
|
89
|
+
variant: level === "irreversible" ? "destructive" : "outline",
|
|
90
|
+
loading: busy,
|
|
91
|
+
disabled: !canConfirm,
|
|
92
|
+
onClick: (event) => {
|
|
93
|
+
if (!canConfirm) {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
const result = onConfirm();
|
|
99
|
+
if (result && typeof result.then === "function") {
|
|
100
|
+
setBusy(true);
|
|
101
|
+
void result.finally(() => {
|
|
102
|
+
setBusy(false);
|
|
103
|
+
onOpenChange(false);
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
onOpenChange(false);
|
|
108
|
+
},
|
|
109
|
+
children: confirmLabel ?? DESTRUCTIVE_VERBS.confirm[level]
|
|
110
|
+
}
|
|
111
|
+
) })
|
|
112
|
+
] })
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
] }) });
|
|
117
|
+
}
|
|
118
|
+
function useConfirm() {
|
|
119
|
+
const [options, setOptions] = React.useState(null);
|
|
120
|
+
const resolver = React.useRef(null);
|
|
121
|
+
const settle = React.useCallback((result) => {
|
|
122
|
+
const resolve = resolver.current;
|
|
123
|
+
resolver.current = null;
|
|
124
|
+
setOptions(null);
|
|
125
|
+
resolve?.(result);
|
|
126
|
+
}, []);
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
return () => {
|
|
129
|
+
resolver.current?.(false);
|
|
130
|
+
resolver.current = null;
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
const ask = React.useCallback((next) => {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
resolver.current = resolve;
|
|
136
|
+
setOptions(next);
|
|
137
|
+
});
|
|
138
|
+
}, []);
|
|
139
|
+
const node = options ? /* @__PURE__ */ jsx(
|
|
140
|
+
ConfirmDialog,
|
|
141
|
+
{
|
|
142
|
+
open: true,
|
|
143
|
+
onOpenChange: (open) => {
|
|
144
|
+
if (!open) settle(false);
|
|
145
|
+
},
|
|
146
|
+
...options,
|
|
147
|
+
onConfirm: async () => {
|
|
148
|
+
await options.onConfirm?.();
|
|
149
|
+
settle(true);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
) : null;
|
|
153
|
+
return [node, ask];
|
|
154
|
+
}
|
|
155
|
+
export {
|
|
156
|
+
AlertDialog,
|
|
157
|
+
AlertDialogOverlay,
|
|
158
|
+
AlertDialogPortal,
|
|
159
|
+
AlertDialogTrigger,
|
|
160
|
+
ConfirmDialog,
|
|
161
|
+
useConfirm
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=alert-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/overlays/alert-dialog.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport { OctagonAlert } from \"@tea-ui/icons\";\nimport { cn } from \"@tea-ui/utils\";\nimport {\n COPY,\n DESTRUCTIVE_VERBS,\n consequenceSentence,\n type ConsequenceLevel,\n} from \"@tea-ui/ux-standards\";\n\nimport { dataSlot } from \"../internal\";\nimport { Button } from \"../inputs/button\";\nimport { Input } from \"../inputs/input\";\nimport { Field, FieldLabel } from \"../inputs/field\";\n\n/**\n * TEA UI — AlertDialog, ConfirmDialog, `useConfirm`.\n *\n * The destructive-action standard, and the fix for an inversion the audit found\n * in both source products: a *reversible* two-step inline confirmation sat\n * beside an *irreversible* `DELETE` that had no confirmation at all. Protection\n * was being spent in the wrong place.\n *\n * Two rules make this correct, and both are enforced here rather than left to\n * the caller:\n *\n * 1. **Cancel is the default focus, and the destructive button is never the\n * primary.** The default variant of the cancel button is `secondary`, the\n * default variant of the confirm button is `outline` for recoverable work\n * and `destructive` only for irreversible work — and it still sits second in\n * the DOM order. A user who presses Enter reflexively must not destroy\n * anything.\n * 2. **The consequence is stated, not implied.** `consequenceSentence()` builds\n * it, so a dialog that says \"wird gelöscht\" can never sit next to a button\n * that says \"Wiederherstellen\".\n *\n * For `irreversible`, `confirmWord` must be typed before the action enables.\n */\nexport const AlertDialog = AlertDialogPrimitive.Root;\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger;\nexport const AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nexport const AlertDialogOverlay = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(function AlertDialogOverlay({ className, ...props }, ref) {\n return (\n <AlertDialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-modal bg-overlay data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0\",\n className,\n )}\n {...dataSlot(\"alert-dialog\", \"overlay\")}\n {...props}\n />\n );\n});\n\nexport interface ConfirmDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** How bad it is. Determines the protection. */\n level: ConsequenceLevel;\n /** The noun phrase, e.g. \"Der Server\" or \"Alle Backups\". */\n what: string;\n title?: string | undefined;\n /** Extra detail beyond the generated consequence sentence. */\n description?: string | undefined;\n /**\n * Required for `irreversible`. The user must type this exactly before the\n * action enables — the last check for an action with no undo.\n */\n confirmWord?: string | undefined;\n confirmLabel?: string | undefined;\n cancelLabel?: string | undefined;\n /** Run when the user confirms. May be async; the dialog waits for it. */\n onConfirm: () => void | Promise<void>;\n}\n\nexport function ConfirmDialog({\n open,\n onOpenChange,\n level,\n what,\n title,\n description,\n confirmWord,\n confirmLabel,\n cancelLabel,\n onConfirm,\n}: ConfirmDialogProps): React.ReactElement {\n const [typed, setTyped] = React.useState(\"\");\n const [busy, setBusy] = React.useState(false);\n\n // The typed word is a gate on the destructive action, never a stored value.\n // It is cleared during render rather than in an effect, so reopening the\n // dialog never inherits a previous confirmation — and never renders one frame\n // with the old word still in the field.\n if (!open && typed !== \"\") setTyped(\"\");\n\n const needsWord = level === \"irreversible\" && Boolean(confirmWord);\n const canConfirm = !needsWord || typed.trim() === confirmWord;\n\n return (\n <AlertDialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n <AlertDialogPrimitive.Portal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Content\n className={cn(\n \"fixed start-1/2 top-1/2 z-modal w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2\",\n \"border border-critical-border bg-surface p-4 shadow-modal\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n )}\n {...dataSlot(\"confirm-dialog\", \"content\")}\n >\n <div className=\"flex items-start gap-3\">\n <OctagonAlert size={20} aria-hidden=\"true\" className=\"mt-0.5 shrink-0 text-critical\" />\n <div className=\"min-w-0 flex-1\">\n <AlertDialogPrimitive.Title className=\"text-title font-semibold text-fg\">\n {title ?? COPY.destructive.confirmTitle}\n </AlertDialogPrimitive.Title>\n <AlertDialogPrimitive.Description className=\"mt-1 text-ui text-fg-muted\">\n {consequenceSentence(what, level)}\n {description ? ` ${description}` : null}\n </AlertDialogPrimitive.Description>\n </div>\n </div>\n\n {needsWord ? (\n <Field className=\"mt-4\" id=\"tea-confirm-word\">\n <FieldLabel>\n {COPY.destructive.typeToConfirm.replace(\"{name}\", confirmWord ?? \"\")}\n </FieldLabel>\n <Input\n value={typed}\n autoComplete=\"off\"\n onChange={(event) => setTyped(event.target.value)}\n aria-invalid={typed.length > 0 && typed.trim() !== confirmWord ? true : undefined}\n />\n </Field>\n ) : null}\n\n <div className=\"mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n <AlertDialogPrimitive.Cancel asChild>\n <Button variant=\"secondary\" disabled={busy}>\n {cancelLabel ?? DESTRUCTIVE_VERBS.cancel}\n </Button>\n </AlertDialogPrimitive.Cancel>\n <AlertDialogPrimitive.Action asChild>\n <Button\n variant={level === \"irreversible\" ? \"destructive\" : \"outline\"}\n loading={busy}\n disabled={!canConfirm}\n onClick={(event) => {\n if (!canConfirm) {\n event.preventDefault();\n return;\n }\n event.preventDefault();\n const result = onConfirm();\n if (result && typeof (result as Promise<void>).then === \"function\") {\n setBusy(true);\n void (result as Promise<void>).finally(() => {\n setBusy(false);\n onOpenChange(false);\n });\n return;\n }\n onOpenChange(false);\n }}\n >\n {confirmLabel ?? DESTRUCTIVE_VERBS.confirm[level]}\n </Button>\n </AlertDialogPrimitive.Action>\n </div>\n </AlertDialogPrimitive.Content>\n </AlertDialogPrimitive.Portal>\n </AlertDialogPrimitive.Root>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n\nexport interface ConfirmOptions extends Omit<ConfirmDialogProps, \"open\" | \"onOpenChange\" | \"onConfirm\"> {\n onConfirm?: () => void | Promise<void>;\n}\n\nexport type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;\n\n/**\n * Promise-based confirmation.\n *\n * ```tsx\n * const [confirm, ask] = useConfirm();\n * const onDelete = async () => {\n * const ok = await ask({ level: \"irreversible\", what: \"Der Server\", confirmWord: \"srv-1\" });\n * if (ok) await remove();\n * };\n * return <>{confirm}<Button onClick={onDelete}>Löschen</Button></>;\n * ```\n *\n * The pending promise is tracked in a ref and resolved in an effect, so an\n * unmount while a dialog is open resolves to `false` instead of leaving the\n * caller's `await` hanging forever.\n */\nexport function useConfirm(): [React.ReactNode, ConfirmFn] {\n const [options, setOptions] = React.useState<ConfirmOptions | null>(null);\n const resolver = React.useRef<((value: boolean) => void) | null>(null);\n\n const settle = React.useCallback((result: boolean) => {\n const resolve = resolver.current;\n resolver.current = null;\n setOptions(null);\n resolve?.(result);\n }, []);\n\n React.useEffect(() => {\n return () => {\n resolver.current?.(false);\n resolver.current = null;\n };\n }, []);\n\n const ask = React.useCallback<ConfirmFn>((next) => {\n return new Promise<boolean>((resolve) => {\n resolver.current = resolve;\n setOptions(next);\n });\n }, []);\n\n const node = options ? (\n <ConfirmDialog\n open\n onOpenChange={(open) => {\n if (!open) settle(false);\n }}\n {...options}\n onConfirm={async () => {\n await options.onConfirm?.();\n settle(true);\n }}\n />\n ) : null;\n\n return [node, ask];\n}\n"],"mappings":"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","names":["AlertDialogOverlay"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Dialog as Dialog$1 } from 'radix-ui';
|
|
4
|
+
import { VariantProps } from '@tea-ui/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TEA UI — Dialog.
|
|
8
|
+
*
|
|
9
|
+
* The audit's dialog defects, all fixed here:
|
|
10
|
+
* - A source project's dialogs shipped an English "Close" label inside an
|
|
11
|
+
* otherwise German product, and one had no accessible name at all. A
|
|
12
|
+
* `DialogTitle` is therefore **required**: if the consumer omits it, a
|
|
13
|
+
* visually hidden fallback is rendered so the dialog always has a name.
|
|
14
|
+
* - Its close button used `focus:outline-none` with no replacement, so keyboard
|
|
15
|
+
* users lost the focus indicator entirely. The focus ring comes from the
|
|
16
|
+
* token base layer and is never removed.
|
|
17
|
+
* - Its content used an 8px hard shadow while every other overlay used 6px.
|
|
18
|
+
* One elevation concept now has three named steps.
|
|
19
|
+
*/
|
|
20
|
+
declare const dialogOverlayVariants: (props?: class_variance_authority_types.ClassProp | undefined) => string;
|
|
21
|
+
declare const dialogContentVariants: (props?: ({
|
|
22
|
+
size?: "sm" | "md" | "lg" | "xl" | "full" | null | undefined;
|
|
23
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
24
|
+
declare const Dialog: React.FC<Dialog$1.DialogProps>;
|
|
25
|
+
declare const DialogTrigger: React.ForwardRefExoticComponent<Dialog$1.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
26
|
+
declare const DialogClose: React.ForwardRefExoticComponent<Dialog$1.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
|
|
27
|
+
declare const DialogPortal: React.FC<Dialog$1.DialogPortalProps>;
|
|
28
|
+
declare const DialogOverlay: React.ForwardRefExoticComponent<Omit<Dialog$1.DialogOverlayProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
29
|
+
interface DialogContentProps extends React.ComponentPropsWithoutRef<typeof Dialog$1.Content>, VariantProps<typeof dialogContentVariants> {
|
|
30
|
+
/** Render the built-in close button. Defaults to `true`. */
|
|
31
|
+
showCloseButton?: boolean | undefined;
|
|
32
|
+
/** Accessible name for that button. */
|
|
33
|
+
closeLabel?: string | undefined;
|
|
34
|
+
/** Accessible name used only when no `DialogTitle` is present. */
|
|
35
|
+
fallbackTitle?: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
declare const DialogContent: React.ForwardRefExoticComponent<DialogContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
38
|
+
declare const DialogHeader: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
39
|
+
declare const DialogBody: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
40
|
+
declare const DialogFooter: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
41
|
+
declare const DialogTitle: React.ForwardRefExoticComponent<Omit<Dialog$1.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
|
|
42
|
+
declare const DialogDescription: React.ForwardRefExoticComponent<Omit<Dialog$1.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
|
|
43
|
+
|
|
44
|
+
export { Dialog, DialogBody, DialogClose, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, dialogContentVariants, dialogOverlayVariants };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
4
|
+
import { X } from "@tea-ui/icons";
|
|
5
|
+
import { cn, cva } from "@tea-ui/utils";
|
|
6
|
+
import { COPY } from "@tea-ui/ux-standards";
|
|
7
|
+
import { dataSlot } from "../internal/index.js";
|
|
8
|
+
const dialogOverlayVariants = cva(
|
|
9
|
+
"fixed inset-0 z-modal bg-overlay data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0"
|
|
10
|
+
);
|
|
11
|
+
const dialogContentVariants = cva(
|
|
12
|
+
[
|
|
13
|
+
"fixed start-1/2 top-1/2 z-modal w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2",
|
|
14
|
+
"border border-line bg-surface shadow-modal",
|
|
15
|
+
"max-h-[calc(100dvh-4rem)] overflow-y-auto",
|
|
16
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
17
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
|
|
18
|
+
],
|
|
19
|
+
{
|
|
20
|
+
variants: {
|
|
21
|
+
size: {
|
|
22
|
+
sm: "max-w-sm",
|
|
23
|
+
md: "max-w-md",
|
|
24
|
+
lg: "max-w-lg",
|
|
25
|
+
xl: "max-w-2xl",
|
|
26
|
+
full: "max-w-4xl"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: { size: "md" }
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
const Dialog = DialogPrimitive.Root;
|
|
33
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
34
|
+
const DialogClose = DialogPrimitive.Close;
|
|
35
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
36
|
+
const DialogOverlay = React.forwardRef(function DialogOverlay2({ className, ...props }, ref) {
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
DialogPrimitive.Overlay,
|
|
39
|
+
{
|
|
40
|
+
ref,
|
|
41
|
+
className: cn(dialogOverlayVariants(), className),
|
|
42
|
+
...dataSlot("dialog", "overlay"),
|
|
43
|
+
...props
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
const DialogContent = React.forwardRef(function DialogContent2({ className, size, children, showCloseButton = true, closeLabel, fallbackTitle, ...props }, ref) {
|
|
48
|
+
const hasTitle = React.Children.toArray(children).some(
|
|
49
|
+
(child) => React.isValidElement(child) && child.type === DialogTitle
|
|
50
|
+
);
|
|
51
|
+
return /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
|
|
52
|
+
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
53
|
+
/* @__PURE__ */ jsxs(
|
|
54
|
+
DialogPrimitive.Content,
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
className: cn(dialogContentVariants({ size }), className),
|
|
58
|
+
...dataSlot("dialog", "content"),
|
|
59
|
+
...props,
|
|
60
|
+
children: [
|
|
61
|
+
hasTitle ? null : /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: fallbackTitle ?? COPY.actions.details }),
|
|
62
|
+
showCloseButton ? /* @__PURE__ */ jsx(
|
|
63
|
+
DialogPrimitive.Close,
|
|
64
|
+
{
|
|
65
|
+
"aria-label": closeLabel ?? COPY.a11y.close,
|
|
66
|
+
className: "absolute end-3 top-3 p-1 text-fg-muted transition-colors hover:text-fg",
|
|
67
|
+
...dataSlot("dialog", "close"),
|
|
68
|
+
children: /* @__PURE__ */ jsx(X, { size: 16, "aria-hidden": "true" })
|
|
69
|
+
}
|
|
70
|
+
) : null,
|
|
71
|
+
children
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
] });
|
|
76
|
+
});
|
|
77
|
+
const DialogHeader = React.forwardRef(
|
|
78
|
+
function DialogHeader2({ className, ...props }, ref) {
|
|
79
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col gap-1 border-b border-line p-4 pe-12", className), ...dataSlot("dialog", "header"), ...props });
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
const DialogBody = React.forwardRef(
|
|
83
|
+
function DialogBody2({ className, ...props }, ref) {
|
|
84
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("p-4", className), ...dataSlot("dialog", "body"), ...props });
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
const DialogFooter = React.forwardRef(
|
|
88
|
+
function DialogFooter2({ className, ...props }, ref) {
|
|
89
|
+
return /* @__PURE__ */ jsx(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
ref,
|
|
93
|
+
className: cn("flex flex-col-reverse gap-2 border-t border-line p-4 sm:flex-row sm:justify-end", className),
|
|
94
|
+
...dataSlot("dialog", "footer"),
|
|
95
|
+
...props
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
const DialogTitle = React.forwardRef(function DialogTitle2({ className, ...props }, ref) {
|
|
101
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ref, className: cn("text-title font-semibold text-fg", className), ...dataSlot("dialog", "title"), ...props });
|
|
102
|
+
});
|
|
103
|
+
const DialogDescription = React.forwardRef(function DialogDescription2({ className, ...props }, ref) {
|
|
104
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ref, className: cn("text-micro text-fg-muted", className), ...dataSlot("dialog", "description"), ...props });
|
|
105
|
+
});
|
|
106
|
+
export {
|
|
107
|
+
Dialog,
|
|
108
|
+
DialogBody,
|
|
109
|
+
DialogClose,
|
|
110
|
+
DialogContent,
|
|
111
|
+
DialogDescription,
|
|
112
|
+
DialogFooter,
|
|
113
|
+
DialogHeader,
|
|
114
|
+
DialogOverlay,
|
|
115
|
+
DialogPortal,
|
|
116
|
+
DialogTitle,
|
|
117
|
+
DialogTrigger,
|
|
118
|
+
dialogContentVariants,
|
|
119
|
+
dialogOverlayVariants
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/overlays/dialog.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport { X } from \"@tea-ui/icons\";\nimport { cn, cva, type VariantProps } from \"@tea-ui/utils\";\nimport { COPY } from \"@tea-ui/ux-standards\";\n\nimport { dataSlot } from \"../internal\";\n\n/**\n * TEA UI — Dialog.\n *\n * The audit's dialog defects, all fixed here:\n * - A source project's dialogs shipped an English \"Close\" label inside an\n * otherwise German product, and one had no accessible name at all. A\n * `DialogTitle` is therefore **required**: if the consumer omits it, a\n * visually hidden fallback is rendered so the dialog always has a name.\n * - Its close button used `focus:outline-none` with no replacement, so keyboard\n * users lost the focus indicator entirely. The focus ring comes from the\n * token base layer and is never removed.\n * - Its content used an 8px hard shadow while every other overlay used 6px.\n * One elevation concept now has three named steps.\n */\nexport const dialogOverlayVariants = cva(\n \"fixed inset-0 z-modal bg-overlay data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0\",\n);\n\nexport const dialogContentVariants = cva(\n [\n \"fixed start-1/2 top-1/2 z-modal w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2\",\n \"border border-line bg-surface shadow-modal\",\n \"max-h-[calc(100dvh-4rem)] overflow-y-auto\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n ],\n {\n variants: {\n size: {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-2xl\",\n full: \"max-w-4xl\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport const Dialog = DialogPrimitive.Root;\nexport const DialogTrigger = DialogPrimitive.Trigger;\nexport const DialogClose = DialogPrimitive.Close;\nexport const DialogPortal = DialogPrimitive.Portal;\n\nexport const DialogOverlay = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(function DialogOverlay({ className, ...props }, ref) {\n return (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(dialogOverlayVariants(), className)}\n {...dataSlot(\"dialog\", \"overlay\")}\n {...props}\n />\n );\n});\n\nexport interface DialogContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,\n VariantProps<typeof dialogContentVariants> {\n /** Render the built-in close button. Defaults to `true`. */\n showCloseButton?: boolean | undefined;\n /** Accessible name for that button. */\n closeLabel?: string | undefined;\n /** Accessible name used only when no `DialogTitle` is present. */\n fallbackTitle?: string | undefined;\n}\n\nexport const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(function DialogContent(\n { className, size, children, showCloseButton = true, closeLabel, fallbackTitle, ...props },\n ref,\n) {\n // A title is mandatory for the accessibility tree. A consumer that forgets\n // one gets a visually hidden one rather than an unnamed dialog.\n const hasTitle = React.Children.toArray(children).some(\n (child) => React.isValidElement(child) && child.type === DialogTitle,\n );\n\n return (\n <DialogPrimitive.Portal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(dialogContentVariants({ size }), className)}\n {...dataSlot(\"dialog\", \"content\")}\n {...props}\n >\n {hasTitle ? null : <DialogTitle className=\"sr-only\">{fallbackTitle ?? COPY.actions.details}</DialogTitle>}\n {showCloseButton ? (\n <DialogPrimitive.Close\n aria-label={closeLabel ?? COPY.a11y.close}\n className=\"absolute end-3 top-3 p-1 text-fg-muted transition-colors hover:text-fg\"\n {...dataSlot(\"dialog\", \"close\")}\n >\n <X size={16} aria-hidden=\"true\" />\n </DialogPrimitive.Close>\n ) : null}\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n});\n\nexport const DialogHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n function DialogHeader({ className, ...props }, ref) {\n return <div ref={ref} className={cn(\"flex flex-col gap-1 border-b border-line p-4 pe-12\", className)} {...dataSlot(\"dialog\", \"header\")} {...props} />;\n },\n);\n\nexport const DialogBody = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n function DialogBody({ className, ...props }, ref) {\n return <div ref={ref} className={cn(\"p-4\", className)} {...dataSlot(\"dialog\", \"body\")} {...props} />;\n },\n);\n\nexport const DialogFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n function DialogFooter({ className, ...props }, ref) {\n return (\n <div\n ref={ref}\n className={cn(\"flex flex-col-reverse gap-2 border-t border-line p-4 sm:flex-row sm:justify-end\", className)}\n {...dataSlot(\"dialog\", \"footer\")}\n {...props}\n />\n );\n },\n);\n\nexport const DialogTitle = React.forwardRef<\n HTMLHeadingElement,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(function DialogTitle({ className, ...props }, ref) {\n return <DialogPrimitive.Title ref={ref} className={cn(\"text-title font-semibold text-fg\", className)} {...dataSlot(\"dialog\", \"title\")} {...props} />;\n});\n\nexport const DialogDescription = React.forwardRef<\n HTMLParagraphElement,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(function DialogDescription({ className, ...props }, ref) {\n return <DialogPrimitive.Description ref={ref} className={cn(\"text-micro text-fg-muted\", className)} {...dataSlot(\"dialog\", \"description\")} {...props} />;\n});\n"],"mappings":"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","names":["DialogOverlay","DialogContent","DialogHeader","DialogBody","DialogFooter","DialogTitle","DialogDescription"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Dialog } from 'radix-ui';
|
|
4
|
+
import { VariantProps } from '@tea-ui/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TEA UI — Drawer and Sheet.
|
|
8
|
+
*
|
|
9
|
+
* The one primitive that replaces three copy-pasted "right drawer" overrides in
|
|
10
|
+
* the audit, and the answer to the behaviour *both* source products needed and
|
|
11
|
+
* each implemented separately: below 1024px the sidebar stops being information
|
|
12
|
+
* and becomes an obstruction, so it collapses into an edge panel.
|
|
13
|
+
*
|
|
14
|
+
* `Drawer` is an edge panel on `Dialog`, so it inherits the focus trap, the
|
|
15
|
+
* scroll lock, Escape and focus restoration. `Sheet` is the bottom variant for
|
|
16
|
+
* small screens, where a side panel would fight the system back gesture.
|
|
17
|
+
*/
|
|
18
|
+
declare const drawerVariants: (props?: ({
|
|
19
|
+
side?: "end" | "bottom" | "start" | "top" | null | undefined;
|
|
20
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
21
|
+
declare const Drawer: React.FC<Dialog.DialogProps>;
|
|
22
|
+
declare const DrawerTrigger: React.ForwardRefExoticComponent<Dialog.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
23
|
+
declare const DrawerClose: React.ForwardRefExoticComponent<Dialog.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
|
|
24
|
+
declare const DrawerPortal: React.FC<Dialog.DialogPortalProps>;
|
|
25
|
+
interface DrawerContentProps extends React.ComponentPropsWithoutRef<typeof Dialog.Content>, VariantProps<typeof drawerVariants> {
|
|
26
|
+
showCloseButton?: boolean | undefined;
|
|
27
|
+
closeLabel?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
declare const DrawerContent: React.ForwardRefExoticComponent<DrawerContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
declare const DrawerHeader: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
31
|
+
/**
|
|
32
|
+
* A drawer, like a dialog, must have an accessible name. Radix wires the title
|
|
33
|
+
* into the content, so a drawer without one is an unnamed dialog — which is why
|
|
34
|
+
* this exists as a part rather than a bare heading.
|
|
35
|
+
*/
|
|
36
|
+
declare const DrawerTitle: React.ForwardRefExoticComponent<Omit<Dialog.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
|
|
37
|
+
declare const DrawerDescription: React.ForwardRefExoticComponent<Omit<Dialog.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
|
|
38
|
+
declare const DrawerBody: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
39
|
+
declare const DrawerFooter: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
40
|
+
/** A bottom sheet: the mobile-safe drawer. */
|
|
41
|
+
declare const Sheet: React.FC<Dialog.DialogProps>;
|
|
42
|
+
declare const SheetTrigger: React.ForwardRefExoticComponent<Dialog.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
43
|
+
declare const SheetClose: React.ForwardRefExoticComponent<Dialog.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
|
|
44
|
+
declare const SheetContent: React.ForwardRefExoticComponent<DrawerContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
45
|
+
declare const SheetHeader: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
46
|
+
declare const SheetBody: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
47
|
+
declare const SheetFooter: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
48
|
+
|
|
49
|
+
export { Drawer, DrawerBody, DrawerClose, DrawerContent, type DrawerContentProps, DrawerDescription, DrawerFooter, DrawerHeader, DrawerPortal, DrawerTitle, DrawerTrigger, Sheet, SheetBody, SheetClose, SheetContent, SheetFooter, SheetHeader, SheetTrigger, drawerVariants };
|