@stackone/malachite 2.70.0 → 2.72.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/COMPONENTS.md +12 -11
- package/dist/components/Accordion.js +59 -53
- package/dist/components/Accordion.js.map +1 -1
- package/dist/components/Alert.js +100 -75
- package/dist/components/Alert.js.map +1 -1
- package/dist/components/Button.js +89 -82
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Modal.js +75 -74
- package/dist/components/Modal.js.map +1 -1
- package/dist/components/PopoverParts.js +35 -35
- package/dist/components/PopoverParts.js.map +1 -1
- package/dist/components/Search.js +30 -29
- package/dist/components/Search.js.map +1 -1
- package/dist/components/SidePanel.js +124 -106
- package/dist/components/SidePanel.js.map +1 -1
- package/dist/components/StatusPill.js +61 -0
- package/dist/components/StatusPill.js.map +1 -0
- package/dist/components/Surface.js +138 -133
- package/dist/components/Surface.js.map +1 -1
- package/dist/components/Toolbar.js +40 -33
- package/dist/components/Toolbar.js.map +1 -1
- package/dist/components/Typography/EllipsisText.js +27 -25
- package/dist/components/Typography/EllipsisText.js.map +1 -1
- package/dist/components/Typography/MarkdownBlock.js +36 -28
- package/dist/components/Typography/MarkdownBlock.js.map +1 -1
- package/dist/components/Typography/Text.js +24 -22
- package/dist/components/Typography/Text.js.map +1 -1
- package/dist/components/WizardLayout.js +81 -67
- package/dist/components/WizardLayout.js.map +1 -1
- package/dist/index.d.ts +65 -2
- package/dist/index.js +356 -354
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sources":["../../src/components/Button.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { getIconOnlySizeClasses, getIconSizeClass } from './buttonSizing';\nimport { resolveRel } from './linkRel';\n\n// Button variant types matching the old component\nexport type ButtonVariant =\n | 'filled'\n | 'outline'\n /**\n * `outline`'s geometry on a ground of its own. For a button sitting on a tinted or\n * patterned surface, where `outline`'s transparent background lets that surface through\n * and the control stops reading as a button.\n */\n | 'surface'\n /**\n * The violet the assistant is named in elsewhere — for a control that hands work to it, or\n * acts on what it produced. Provenance, not a severity: it says who is being asked, not\n * that anything is wrong.\n */\n | 'assistant'\n | 'destructive'\n | 'success'\n | 'warning'\n | 'error'\n | 'ghost'\n | 'no-style';\n\nexport type ButtonSize = 'xsmall' | 'small' | 'medium' | 'large';\n\nexport type ButtonRadius = 'md' | 'lg' | 'xl';\n\ninterface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant: ButtonVariant;\n size?: ButtonSize;\n radius?: ButtonRadius;\n loading?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n iconPosition?: 'start' | 'end';\n /** Before the label at its own size, never squeezed (an avatar); pairs with an end `icon`. */\n leading?: React.ReactNode;\n /** `start` aligns the label to the leading edge and pins an end `icon` to the far edge. */\n contentAlign?: 'center' | 'start';\n /** Fills the container's width. */\n flexGrow?: boolean;\n fill?: boolean;\n tooltip?: string;\n tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';\n iconOnly?: boolean;\n children?: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement & HTMLAnchorElement>;\n /** Renders the button as a real anchor, so a link action is never an <a> nested in a <button>. */\n href?: string;\n target?: React.HTMLAttributeAnchorTarget;\n rel?: string;\n}\n\n// Geometry from the Figma component library, which is variant-independent.\n// `min-h` not `h`: Tailwind emits `h-*` after `min-h-*`, so a hard height would beat a\n// consumer's own `h-*`. Paired with `whitespace-nowrap` (as Figma has it) so a long label\n// cannot wrap into a line the height would clip.\nconst getSizeClasses = (size: ButtonSize) => {\n switch (size) {\n // Figma has no text X-Small button, only the icon-only square — nothing to align to.\n case 'xsmall':\n return 'px-2 py-1 text-xs';\n case 'small':\n return 'min-h-[var(--malachite-control-height-small,28px)] px-[10px] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'medium':\n return 'min-h-[var(--malachite-control-height-medium,32px)] px-[10px] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'large':\n return 'min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n default:\n return 'min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n }\n};\n\n// `rounded-*` reads the malachite radius tokens via `@theme`, so these follow the theme.\n// An explicit `radius` prop still wins, via getRadiusClass.\nconst getDefaultRadiusClass = (size: ButtonSize, iconOnly: boolean): string => {\n switch (size) {\n // Only the square rounds at 8px — text buttons at this size keep their 6px corner.\n case 'xsmall':\n return iconOnly ? 'rounded-lg' : 'rounded-md';\n case 'small':\n return 'rounded-[var(--malachite-radius-10,10px)]';\n case 'medium':\n return 'rounded-xl';\n case 'large':\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n default:\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n }\n};\n\n// Figma pairs a 6px icon gap with the 13px tiers and 8px with the 15px tier.\nconst getIconGapClass = (size: ButtonSize, position: 'start' | 'end'): string => {\n const start = position === 'start';\n switch (size) {\n case 'small':\n case 'medium':\n return start ? 'mr-[6px]' : 'ml-[6px]';\n default:\n return start ? 'mr-2' : 'ml-2';\n }\n};\n\nconst getRadiusClass = (radius: ButtonRadius): string => {\n switch (radius) {\n case 'md':\n return 'rounded-md';\n case 'lg':\n return 'rounded-lg';\n case 'xl':\n return 'rounded-xl';\n default:\n return 'rounded-xl';\n }\n};\n\n// Focus ring: a `focus-visible` outline (keyboard only), coloured to match the variant.\n// Uses `outline` rather than Tailwind's `ring-*` because ring composes `--tw-ring-*`\n// custom properties via box-shadow that rely on document-level @property registration\n// and silently don't paint inside a shadow root (same class of issue as transform\n// utilities). The classes are full literals so Tailwind's JIT can generate them.\nconst FOCUS_OUTLINE = {\n neutral: 'focus-visible:[outline:2px_solid_var(--malachite-text-primary)]',\n destructive: 'focus-visible:[outline:2px_solid_var(--malachite-text-destructive)]',\n success: 'focus-visible:[outline:2px_solid_var(--malachite-text-success)]',\n warning: 'focus-visible:[outline:2px_solid_var(--malachite-text-warning)]',\n error: 'focus-visible:[outline:2px_solid_var(--malachite-text-error)]',\n assistant: 'focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]',\n};\n\nconst getVariantClasses = (variant: ButtonVariant, radiusClass: string): string => {\n const baseClasses = `font-medium transition-[color,background-color,border-color,opacity,box-shadow] duration-200 ease-in-out outline-none focus-visible:outline-offset-2 border border-solid ${radiusClass} cursor-pointer`;\n\n switch (variant) {\n case 'filled':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n\n case 'outline':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-outline shadow-default`;\n\n case 'surface':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-surface shadow-default`;\n\n case 'assistant':\n return `${baseClasses} ${FOCUS_OUTLINE.assistant} btn-assistant shadow-default`;\n\n case 'destructive':\n return `${baseClasses} ${FOCUS_OUTLINE.destructive} btn-destructive`;\n\n case 'success':\n return `${baseClasses} ${FOCUS_OUTLINE.success} btn-glass-success`;\n\n case 'warning':\n return `${baseClasses} ${FOCUS_OUTLINE.warning} btn-glass-warning`;\n\n case 'error':\n return `${baseClasses} ${FOCUS_OUTLINE.error} btn-glass-error`;\n\n case 'ghost':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-ghost`;\n\n case 'no-style':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-no-style border-none`;\n\n default:\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n }\n};\n\nconst LoadingSpinner = () => (\n <svg\n className=\"animate-spin -ml-1 mr-2 h-4 w-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n);\n\nexport const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n variant,\n size = 'large',\n radius,\n loading = false,\n disabled = false,\n icon,\n iconPosition = 'start',\n leading,\n contentAlign = 'center',\n flexGrow = false,\n fill = false,\n tooltip,\n tooltipPlacement = 'top',\n iconOnly = false,\n children,\n className = '',\n href,\n target,\n rel,\n ...props\n },\n ref,\n ) => {\n const radiusClass = radius ? getRadiusClass(radius) : getDefaultRadiusClass(size, iconOnly);\n const isInactive = disabled || loading;\n const alignStart = contentAlign === 'start';\n const baseClasses = [\n alignStart\n ? 'flex items-center justify-start text-left'\n : 'flex items-center justify-center',\n iconOnly ? `p-0 ${getIconOnlySizeClasses(size)}` : getSizeClasses(size),\n getVariantClasses(variant, radiusClass),\n flexGrow ? 'w-full' : '',\n fill ? 'max-w-full' : '',\n isInactive ? 'cursor-not-allowed' : '',\n tooltip && isInactive ? 'pointer-events-none' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const label = alignStart ? (\n <span className=\"flex min-w-0 flex-1 items-center\">{children}</span>\n ) : (\n children\n );\n\n const leadingSlot = leading ? (\n <span\n className={\n children\n ? 'btn-leading inline-flex shrink-0 items-center me-2'\n : 'btn-leading inline-flex shrink-0 items-center'\n }\n >\n {leading}\n </span>\n ) : null;\n\n const renderContent = () => {\n if (loading) {\n return (\n <>\n {leadingSlot}\n <LoadingSpinner />\n {label}\n </>\n );\n }\n\n const iconWrapperClasses = `inline-flex items-center shrink-0 ${getIconSizeClass(size)}`;\n\n if (icon && iconPosition === 'start') {\n return (\n <>\n {leadingSlot}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'start')}` : ''}`}\n >\n {icon}\n </span>\n {label}\n </>\n );\n }\n\n if (icon && iconPosition === 'end') {\n return (\n <>\n {leadingSlot}\n {label}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'end')}` : ''}`}\n >\n {icon}\n </span>\n </>\n );\n }\n\n return (\n <>\n {leadingSlot}\n {label}\n </>\n );\n };\n\n // An anchor cannot be disabled, so an inactive link still renders as a <button>.\n const button =\n href && !isInactive ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolveRel(rel, target)}\n className={baseClasses}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {renderContent()}\n </a>\n ) : (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n disabled={isInactive}\n className={baseClasses}\n {...props}\n >\n {renderContent()}\n </button>\n );\n\n if (tooltip) {\n const trigger = isInactive ? (\n <span className={`inline-flex cursor-not-allowed${flexGrow ? ' w-full' : ''}`}>\n {button}\n </span>\n ) : (\n button\n );\n return (\n <Tooltip text={tooltip} placement={tooltipPlacement} fill={flexGrow}>\n {trigger}\n </Tooltip>\n );\n }\n\n return button;\n },\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":["getSizeClasses","size","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getRadiusClass","radius","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses","LoadingSpinner","jsxs","jsx","Button","forwardRef","loading","disabled","icon","iconPosition","leading","contentAlign","flexGrow","fill","tooltip","tooltipPlacement","children","className","href","target","rel","props","ref","isInactive","alignStart","getIconOnlySizeClasses","label","leadingSlot","renderContent","Fragment","iconWrapperClasses","getIconSizeClass","button","resolveRel","Tooltip"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Button.js","sources":["../../src/components/Button.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { getIconOnlySizeClasses, getIconSizeClass } from './buttonSizing';\nimport { resolveRel } from './linkRel';\n\n// Button variant types matching the old component\nexport type ButtonVariant =\n | 'filled'\n | 'outline'\n /**\n * `outline`'s geometry on a ground of its own. For a button sitting on a tinted or\n * patterned surface, where `outline`'s transparent background lets that surface through\n * and the control stops reading as a button.\n */\n | 'surface'\n /**\n * The violet the assistant is named in elsewhere — for a control that hands work to it, or\n * acts on what it produced. Provenance, not a severity: it says who is being asked, not\n * that anything is wrong.\n */\n | 'assistant'\n | 'destructive'\n | 'success'\n | 'warning'\n | 'error'\n | 'ghost'\n /** Text only, in the accent colour, for a secondary action beside a primary one. */\n | 'quiet'\n | 'no-style';\n\nexport type ButtonSize = 'xsmall' | 'small' | 'medium' | 'large';\n\nexport type ButtonRadius = 'md' | 'lg' | 'xl';\n\ninterface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant: ButtonVariant;\n size?: ButtonSize;\n radius?: ButtonRadius;\n loading?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n iconPosition?: 'start' | 'end';\n /** Before the label at its own size, never squeezed (an avatar); pairs with an end `icon`. */\n leading?: React.ReactNode;\n /** `start` aligns the label to the leading edge and pins an end `icon` to the far edge. */\n contentAlign?: 'center' | 'start';\n /** Fills the container's width. */\n flexGrow?: boolean;\n /** `wide` holds a 120px floor for a primary action. */\n width?: 'auto' | 'wide';\n fill?: boolean;\n tooltip?: string;\n tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';\n iconOnly?: boolean;\n /** Pulls a borderless button out by its own padding, so its label lines up with the text around it. */\n flush?: boolean;\n children?: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement & HTMLAnchorElement>;\n /** Renders the button as a real anchor, so a link action is never an <a> nested in a <button>. */\n href?: string;\n target?: React.HTMLAttributeAnchorTarget;\n rel?: string;\n}\n\n// Geometry from the Figma component library, which is variant-independent.\n// `min-h` not `h`: Tailwind emits `h-*` after `min-h-*`, so a hard height would beat a\n// consumer's own `h-*`. Paired with `whitespace-nowrap` (as Figma has it) so a long label\n// cannot wrap into a line the height would clip.\nconst getSizeClasses = (size: ButtonSize) => {\n switch (size) {\n // Figma has no text X-Small button, only the icon-only square — nothing to align to.\n case 'xsmall':\n return '[--btn-px:calc(var(--spacing)*2)] px-[var(--btn-px)] py-1 text-xs';\n case 'small':\n return 'min-h-[var(--malachite-control-height-small,28px)] [--btn-px:10px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'medium':\n return 'min-h-[var(--malachite-control-height-medium,32px)] [--btn-px:10px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'large':\n return 'min-h-[var(--malachite-control-height-large,42px)] [--btn-px:12px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n default:\n return 'min-h-[var(--malachite-control-height-large,42px)] [--btn-px:12px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n }\n};\n\n// The 1px is the transparent border, which `no-style` drops.\nconst FLUSH_MARGIN = '-mx-[calc(var(--btn-px)+1px)]';\nconst FLUSH_MARGIN_BORDERLESS = '-mx-[var(--btn-px)]';\n\n// `rounded-*` reads the malachite radius tokens via `@theme`, so these follow the theme.\n// An explicit `radius` prop still wins, via getRadiusClass.\nconst getDefaultRadiusClass = (size: ButtonSize, iconOnly: boolean): string => {\n switch (size) {\n // Only the square rounds at 8px — text buttons at this size keep their 6px corner.\n case 'xsmall':\n return iconOnly ? 'rounded-lg' : 'rounded-md';\n case 'small':\n return 'rounded-[var(--malachite-radius-10,10px)]';\n case 'medium':\n return 'rounded-xl';\n case 'large':\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n default:\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n }\n};\n\n// Figma pairs a 6px icon gap with the 13px tiers and 8px with the 15px tier.\nconst getIconGapClass = (size: ButtonSize, position: 'start' | 'end'): string => {\n const start = position === 'start';\n switch (size) {\n case 'small':\n case 'medium':\n return start ? 'mr-[6px]' : 'ml-[6px]';\n default:\n return start ? 'mr-2' : 'ml-2';\n }\n};\n\nconst getRadiusClass = (radius: ButtonRadius): string => {\n switch (radius) {\n case 'md':\n return 'rounded-md';\n case 'lg':\n return 'rounded-lg';\n case 'xl':\n return 'rounded-xl';\n default:\n return 'rounded-xl';\n }\n};\n\n// Focus ring: a `focus-visible` outline (keyboard only), coloured to match the variant.\n// Uses `outline` rather than Tailwind's `ring-*` because ring composes `--tw-ring-*`\n// custom properties via box-shadow that rely on document-level @property registration\n// and silently don't paint inside a shadow root (same class of issue as transform\n// utilities). The classes are full literals so Tailwind's JIT can generate them.\nconst FOCUS_OUTLINE = {\n neutral: 'focus-visible:[outline:2px_solid_var(--malachite-text-primary)]',\n destructive: 'focus-visible:[outline:2px_solid_var(--malachite-text-destructive)]',\n success: 'focus-visible:[outline:2px_solid_var(--malachite-text-success)]',\n warning: 'focus-visible:[outline:2px_solid_var(--malachite-text-warning)]',\n error: 'focus-visible:[outline:2px_solid_var(--malachite-text-error)]',\n assistant: 'focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]',\n accent: 'focus-visible:[outline:2px_solid_var(--malachite-accent-foreground)]',\n};\n\nconst getVariantClasses = (variant: ButtonVariant, radiusClass: string): string => {\n const baseClasses = `font-medium transition-[color,background-color,border-color,opacity,box-shadow] duration-200 ease-in-out outline-none focus-visible:outline-offset-2 border border-solid ${radiusClass} cursor-pointer`;\n\n switch (variant) {\n case 'filled':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n\n case 'outline':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-outline shadow-default`;\n\n case 'surface':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-surface shadow-default`;\n\n case 'assistant':\n return `${baseClasses} ${FOCUS_OUTLINE.assistant} btn-assistant shadow-default`;\n\n case 'destructive':\n return `${baseClasses} ${FOCUS_OUTLINE.destructive} btn-destructive`;\n\n case 'success':\n return `${baseClasses} ${FOCUS_OUTLINE.success} btn-glass-success`;\n\n case 'warning':\n return `${baseClasses} ${FOCUS_OUTLINE.warning} btn-glass-warning`;\n\n case 'error':\n return `${baseClasses} ${FOCUS_OUTLINE.error} btn-glass-error`;\n\n case 'ghost':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-ghost`;\n\n case 'quiet':\n return `${baseClasses} ${FOCUS_OUTLINE.accent} btn-quiet`;\n\n case 'no-style':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-no-style border-none`;\n\n default:\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n }\n};\n\nconst LoadingSpinner = () => (\n <svg\n className=\"animate-spin -ml-1 mr-2 h-4 w-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n);\n\nexport const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n variant,\n size = 'large',\n radius,\n loading = false,\n disabled = false,\n icon,\n iconPosition = 'start',\n leading,\n contentAlign = 'center',\n flexGrow = false,\n width = 'auto',\n fill = false,\n tooltip,\n tooltipPlacement = 'top',\n iconOnly = false,\n flush = false,\n children,\n className = '',\n href,\n target,\n rel,\n ...props\n },\n ref,\n ) => {\n const radiusClass = radius ? getRadiusClass(radius) : getDefaultRadiusClass(size, iconOnly);\n const isInactive = disabled || loading;\n const alignStart = contentAlign === 'start';\n const baseClasses = [\n alignStart\n ? 'flex items-center justify-start text-left'\n : 'flex items-center justify-center',\n iconOnly ? `p-0 ${getIconOnlySizeClasses(size)}` : getSizeClasses(size),\n getVariantClasses(variant, radiusClass),\n flexGrow ? 'w-full' : '',\n width === 'wide' ? 'min-w-[120px]' : '',\n fill ? 'max-w-full' : '',\n isInactive ? 'cursor-not-allowed' : '',\n tooltip && isInactive ? 'pointer-events-none' : '',\n flush && !iconOnly\n ? variant === 'no-style'\n ? FLUSH_MARGIN_BORDERLESS\n : FLUSH_MARGIN\n : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const label = alignStart ? (\n <span className=\"flex min-w-0 flex-1 items-center\">{children}</span>\n ) : (\n children\n );\n\n const leadingSlot = leading ? (\n <span\n className={\n children\n ? 'btn-leading inline-flex shrink-0 items-center me-2'\n : 'btn-leading inline-flex shrink-0 items-center'\n }\n >\n {leading}\n </span>\n ) : null;\n\n const renderContent = () => {\n if (loading) {\n return (\n <>\n {leadingSlot}\n <LoadingSpinner />\n {label}\n </>\n );\n }\n\n const iconWrapperClasses = `inline-flex items-center shrink-0 ${getIconSizeClass(size)}`;\n\n if (icon && iconPosition === 'start') {\n return (\n <>\n {leadingSlot}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'start')}` : ''}`}\n >\n {icon}\n </span>\n {label}\n </>\n );\n }\n\n if (icon && iconPosition === 'end') {\n return (\n <>\n {leadingSlot}\n {label}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'end')}` : ''}`}\n >\n {icon}\n </span>\n </>\n );\n }\n\n return (\n <>\n {leadingSlot}\n {label}\n </>\n );\n };\n\n // An anchor cannot be disabled, so an inactive link still renders as a <button>.\n const button =\n href && !isInactive ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolveRel(rel, target)}\n className={baseClasses}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {renderContent()}\n </a>\n ) : (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n disabled={isInactive}\n className={baseClasses}\n {...props}\n >\n {renderContent()}\n </button>\n );\n\n if (tooltip) {\n const trigger = isInactive ? (\n <span className={`inline-flex cursor-not-allowed${flexGrow ? ' w-full' : ''}`}>\n {button}\n </span>\n ) : (\n button\n );\n return (\n <Tooltip text={tooltip} placement={tooltipPlacement} fill={flexGrow}>\n {trigger}\n </Tooltip>\n );\n }\n\n return button;\n },\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":["getSizeClasses","size","FLUSH_MARGIN","FLUSH_MARGIN_BORDERLESS","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getRadiusClass","radius","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses","LoadingSpinner","jsxs","jsx","Button","forwardRef","loading","disabled","icon","iconPosition","leading","contentAlign","flexGrow","width","fill","tooltip","tooltipPlacement","flush","children","className","href","target","rel","props","ref","isInactive","alignStart","getIconOnlySizeClasses","label","leadingSlot","renderContent","Fragment","iconWrapperClasses","getIconSizeClass","button","resolveRel","Tooltip"],"mappings":"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"}
|
package/dist/components/Modal.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { jsx as r, jsxs as l, Fragment as
|
|
2
|
-
import { useId as
|
|
3
|
-
import { createPortal as
|
|
4
|
-
import { ShadowRootWrapper as
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { getDeepActiveElement as
|
|
8
|
-
import { GLASS_SURFACE_STYLE as
|
|
9
|
-
import { MODAL_TRAY_BACKGROUND as
|
|
10
|
-
import { useOverlayBehaviour as
|
|
11
|
-
import { OVERLAY_ELEVATED_ATTR as
|
|
1
|
+
import { jsx as r, jsxs as l, Fragment as j } from "react/jsx-runtime";
|
|
2
|
+
import { useId as B, useState as h, useRef as n, useEffect as P, useCallback as V } from "react";
|
|
3
|
+
import { createPortal as le } from "react-dom";
|
|
4
|
+
import { ShadowRootWrapper as te } from "../ShadowRootWrapper.js";
|
|
5
|
+
import ie from "./CalloutStrip.js";
|
|
6
|
+
import ne from "./CustomIcons/DismissIcon.js";
|
|
7
|
+
import { getDeepActiveElement as oe } from "./activeElement.js";
|
|
8
|
+
import { GLASS_SURFACE_STYLE as se } from "./glassSurface.js";
|
|
9
|
+
import { MODAL_TRAY_BACKGROUND as ce, MODAL_TRAY_CAPTION_COLOR as de } from "./modalTray.js";
|
|
10
|
+
import { useOverlayBehaviour as me, OVERLAY_ICON_BUTTON_CLASSES as ue } from "./overlayBehaviour.js";
|
|
11
|
+
import { OVERLAY_ELEVATED_ATTR as pe, overlayIdentity as F } from "./portalOverlay.js";
|
|
12
12
|
const U = {
|
|
13
13
|
small: "420px",
|
|
14
14
|
medium: "612px",
|
|
15
15
|
large: "760px"
|
|
16
|
-
}, v = 200,
|
|
16
|
+
}, v = 200, xe = "flex-[0_1_auto] h-[min(600px,calc(100dvh-160px))]", Ie = ({
|
|
17
17
|
open: c,
|
|
18
18
|
onClose: d,
|
|
19
19
|
title: u,
|
|
@@ -24,64 +24,65 @@ const U = {
|
|
|
24
24
|
tray: e,
|
|
25
25
|
headerDivider: w = !1,
|
|
26
26
|
footer: R,
|
|
27
|
-
size:
|
|
27
|
+
size: _ = "medium",
|
|
28
28
|
showCloseButton: W = !0,
|
|
29
29
|
closeOnOverlayClick: z = !0,
|
|
30
|
-
bodyPadding:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"aria-
|
|
36
|
-
"aria-
|
|
30
|
+
bodyPadding: A = !0,
|
|
31
|
+
bodyHeight: G = "auto",
|
|
32
|
+
callouts: x,
|
|
33
|
+
renderLink: q,
|
|
34
|
+
role: H = "dialog",
|
|
35
|
+
"aria-label": K,
|
|
36
|
+
"aria-labelledby": T,
|
|
37
|
+
"aria-describedby": X,
|
|
37
38
|
busy: o = !1,
|
|
38
|
-
children:
|
|
39
|
+
children: I
|
|
39
40
|
}) => {
|
|
40
|
-
const k =
|
|
41
|
-
|
|
42
|
-
L.current = o,
|
|
41
|
+
const k = B(), S = B(), m = (x == null ? void 0 : x.filter((a) => a.message.trim())) ?? [], [f, $] = h(!1), [Z, O] = h(!1), [J, E] = h(!1), C = n(null), s = n(null), t = n(null), L = n(o), D = n(d);
|
|
42
|
+
P(() => {
|
|
43
|
+
L.current = o, D.current = d;
|
|
43
44
|
}, [o, d]);
|
|
44
|
-
const
|
|
45
|
-
L.current ||
|
|
46
|
-
}, []),
|
|
47
|
-
ref:
|
|
48
|
-
isTopmost:
|
|
49
|
-
zIndex:
|
|
50
|
-
elevated:
|
|
51
|
-
} =
|
|
45
|
+
const M = V(() => {
|
|
46
|
+
L.current || D.current();
|
|
47
|
+
}, []), y = n(null), {
|
|
48
|
+
ref: Y,
|
|
49
|
+
isTopmost: Q,
|
|
50
|
+
zIndex: ee,
|
|
51
|
+
elevated: re
|
|
52
|
+
} = me({
|
|
52
53
|
open: c,
|
|
53
54
|
onClose: d,
|
|
54
|
-
hostRef:
|
|
55
|
-
}),
|
|
55
|
+
hostRef: y
|
|
56
|
+
}), ae = V(
|
|
56
57
|
(a) => {
|
|
57
|
-
C.current = a,
|
|
58
|
+
C.current = a, Y(a);
|
|
58
59
|
},
|
|
59
|
-
[
|
|
60
|
+
[Y]
|
|
60
61
|
);
|
|
61
|
-
return
|
|
62
|
+
return P(() => {
|
|
62
63
|
var a;
|
|
63
64
|
if (c)
|
|
64
|
-
|
|
65
|
+
O(!0), E(!1), t.current && (clearTimeout(t.current), t.current = null), s.current = requestAnimationFrame(() => {
|
|
65
66
|
s.current = null, $(!0);
|
|
66
67
|
});
|
|
67
68
|
else {
|
|
68
|
-
const i =
|
|
69
|
-
i && ((a = C.current) != null && a.contains(i)) && i.blur(),
|
|
70
|
-
t.current = null,
|
|
69
|
+
const i = oe();
|
|
70
|
+
i && ((a = C.current) != null && a.contains(i)) && i.blur(), E(!0), $(!1), t.current = setTimeout(() => {
|
|
71
|
+
t.current = null, O(!1);
|
|
71
72
|
}, v);
|
|
72
73
|
}
|
|
73
74
|
return () => {
|
|
74
75
|
s.current !== null && (cancelAnimationFrame(s.current), s.current = null), t.current && (clearTimeout(t.current), t.current = null);
|
|
75
76
|
};
|
|
76
|
-
}, [c]),
|
|
77
|
+
}, [c]), Z ? le(
|
|
77
78
|
// Host carries the stacking context — ShadowRootWrapper's `isolation` would otherwise
|
|
78
79
|
// flatten our z-index. Not promoted to the top layer, so host dialogs can cover us.
|
|
79
80
|
/* @__PURE__ */ r(
|
|
80
|
-
|
|
81
|
+
te,
|
|
81
82
|
{
|
|
82
|
-
hostRef:
|
|
83
|
-
hostStyle: { position: "relative", zIndex:
|
|
84
|
-
hostProps:
|
|
83
|
+
hostRef: y,
|
|
84
|
+
hostStyle: { position: "relative", zIndex: ee },
|
|
85
|
+
hostProps: re ? { ...F("modal"), [pe]: "" } : F("modal"),
|
|
85
86
|
children: /* @__PURE__ */ l(
|
|
86
87
|
"div",
|
|
87
88
|
{
|
|
@@ -91,36 +92,36 @@ const U = {
|
|
|
91
92
|
"div",
|
|
92
93
|
{
|
|
93
94
|
"aria-hidden": "true",
|
|
94
|
-
onClick: z ?
|
|
95
|
-
className: `absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${
|
|
95
|
+
onClick: z ? M : void 0,
|
|
96
|
+
className: `absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${f ? "opacity-100" : "opacity-0"}`
|
|
96
97
|
}
|
|
97
98
|
),
|
|
98
99
|
/* @__PURE__ */ l(
|
|
99
100
|
"div",
|
|
100
101
|
{
|
|
101
|
-
ref:
|
|
102
|
-
role:
|
|
103
|
-
"aria-hidden":
|
|
104
|
-
"aria-modal":
|
|
102
|
+
ref: ae,
|
|
103
|
+
role: H,
|
|
104
|
+
"aria-hidden": J ? "true" : void 0,
|
|
105
|
+
"aria-modal": Q ? "true" : void 0,
|
|
105
106
|
"aria-describedby": [
|
|
106
107
|
...m.map((a, i) => `${S}-${i}`),
|
|
107
|
-
|
|
108
|
+
X,
|
|
108
109
|
e != null && e.caption ? k : null
|
|
109
110
|
].filter(Boolean).join(" ") || void 0,
|
|
110
111
|
"aria-busy": o || void 0,
|
|
111
112
|
tabIndex: -1,
|
|
112
|
-
"aria-label":
|
|
113
|
-
"aria-labelledby":
|
|
113
|
+
"aria-label": T ? void 0 : K ?? (typeof u == "string" ? u : void 0),
|
|
114
|
+
"aria-labelledby": T,
|
|
114
115
|
className: e ? "relative flex max-h-full w-full flex-col gap-[4px] rounded-[20px] p-[4px] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]" : "relative flex max-h-full w-full flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]",
|
|
115
116
|
style: {
|
|
116
|
-
maxWidth: e ? `calc(${U[
|
|
117
|
+
maxWidth: e ? `calc(${U[_]} + 8px)` : U[_],
|
|
117
118
|
...e ? {
|
|
118
|
-
backgroundColor:
|
|
119
|
+
backgroundColor: ce[e.tone ?? "neutral"]
|
|
119
120
|
} : {},
|
|
120
121
|
// Inline: Tailwind scale/translate utilities silently break inside
|
|
121
122
|
// shadow roots (see CLAUDE.md Shadow DOM caveats).
|
|
122
|
-
transform:
|
|
123
|
-
opacity:
|
|
123
|
+
transform: f ? "scale(1)" : "scale(0.96)",
|
|
124
|
+
opacity: f ? 1 : 0,
|
|
124
125
|
transition: `transform ${v}ms ease-out, opacity ${v}ms ease-out`
|
|
125
126
|
},
|
|
126
127
|
children: [
|
|
@@ -142,7 +143,7 @@ const U = {
|
|
|
142
143
|
p ? /* @__PURE__ */ r("span", { className: "text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]", children: p }) : null
|
|
143
144
|
] })
|
|
144
145
|
] }),
|
|
145
|
-
N ? /* @__PURE__ */ l(
|
|
146
|
+
N ? /* @__PURE__ */ l(j, { children: [
|
|
146
147
|
/* @__PURE__ */ r(
|
|
147
148
|
"span",
|
|
148
149
|
{
|
|
@@ -159,10 +160,10 @@ const U = {
|
|
|
159
160
|
{
|
|
160
161
|
type: "button",
|
|
161
162
|
"aria-label": "Close",
|
|
162
|
-
onClick:
|
|
163
|
+
onClick: M,
|
|
163
164
|
disabled: o,
|
|
164
|
-
className:
|
|
165
|
-
children: /* @__PURE__ */ r(
|
|
165
|
+
className: ue,
|
|
166
|
+
children: /* @__PURE__ */ r(ne, { size: 10 })
|
|
166
167
|
}
|
|
167
168
|
) : null
|
|
168
169
|
]
|
|
@@ -171,25 +172,25 @@ const U = {
|
|
|
171
172
|
/* @__PURE__ */ l(
|
|
172
173
|
"div",
|
|
173
174
|
{
|
|
174
|
-
className: `min-h-0 flex-1 overflow-y-auto ${
|
|
175
|
+
className: `min-h-0 ${G === "tall" ? xe : "flex-1"} overflow-y-auto ${A && m.length === 0 ? "px-[20px]" : ""}`,
|
|
175
176
|
children: [
|
|
176
177
|
m.map((a, i) => /* @__PURE__ */ r(
|
|
177
|
-
|
|
178
|
+
ie,
|
|
178
179
|
{
|
|
179
180
|
id: `${S}-${i}`,
|
|
180
181
|
type: a.type,
|
|
181
182
|
message: a.message,
|
|
182
|
-
renderLink:
|
|
183
|
+
renderLink: q
|
|
183
184
|
},
|
|
184
185
|
i
|
|
185
186
|
)),
|
|
186
|
-
|
|
187
|
+
A ? /* @__PURE__ */ r(
|
|
187
188
|
"div",
|
|
188
189
|
{
|
|
189
190
|
className: m.length > 0 ? "px-[20px] pt-[18px] pb-[20px]" : `pb-[20px] ${w ? "pt-[18px]" : ""}`,
|
|
190
|
-
children:
|
|
191
|
+
children: I
|
|
191
192
|
}
|
|
192
|
-
) :
|
|
193
|
+
) : I
|
|
193
194
|
]
|
|
194
195
|
}
|
|
195
196
|
),
|
|
@@ -197,7 +198,7 @@ const U = {
|
|
|
197
198
|
"div",
|
|
198
199
|
{
|
|
199
200
|
className: "flex min-h-[56px] shrink-0 items-center border-0 border-t border-solid border-[var(--malachite-card-border)] px-[12px]",
|
|
200
|
-
style:
|
|
201
|
+
style: se,
|
|
201
202
|
children: R
|
|
202
203
|
}
|
|
203
204
|
) : null
|
|
@@ -209,9 +210,9 @@ const U = {
|
|
|
209
210
|
{
|
|
210
211
|
id: k,
|
|
211
212
|
className: "flex h-[48px] shrink-0 items-center justify-center px-[16px] text-[13px] tracking-[0.13px]",
|
|
212
|
-
style: { color:
|
|
213
|
+
style: { color: de[e.tone ?? "neutral"] },
|
|
213
214
|
children: /* @__PURE__ */ l("span", { children: [
|
|
214
|
-
e.caption.label ? /* @__PURE__ */ l(
|
|
215
|
+
e.caption.label ? /* @__PURE__ */ l(j, { children: [
|
|
215
216
|
/* @__PURE__ */ r("span", { className: "font-medium", children: e.caption.label }),
|
|
216
217
|
" "
|
|
217
218
|
] }) : null,
|
|
@@ -231,7 +232,7 @@ const U = {
|
|
|
231
232
|
) : null;
|
|
232
233
|
};
|
|
233
234
|
export {
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
Ie as Modal,
|
|
236
|
+
Ie as default
|
|
236
237
|
};
|
|
237
238
|
//# sourceMappingURL=Modal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","sources":["../../src/components/Modal.tsx"],"sourcesContent":["import type React from 'react';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport { getDeepActiveElement } from './activeElement';\nimport { GLASS_SURFACE_STYLE } from './glassSurface';\nimport { MODAL_TRAY_BACKGROUND, MODAL_TRAY_CAPTION_COLOR, type ModalTrayTone } from './modalTray';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport type ModalSize = 'small' | 'medium' | 'large';\n\n/** Widths keyed to the size union — a raw `width` prop would let a consumer off the scale. */\nconst SIZE_MAX_WIDTH: Record<ModalSize, string> = {\n small: '420px',\n medium: '612px',\n large: '760px',\n};\n\nconst TRANSITION_MS = 200;\n\nexport interface ModalProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n /** Secondary line beneath the title. */\n subtitle?: React.ReactNode;\n /** Leading header slot, typically an icon or logo. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n titleAdornment?: React.ReactNode;\n tray?: { tone?: ModalTrayTone; caption?: { label?: string; message: string } };\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Pinned action row below the scrolling body. */\n footer?: React.ReactNode;\n size?: ModalSize;\n /** Default: true. */\n showCloseButton?: boolean;\n /** Default: true. Turn off for a dialog whose choice must be made deliberately. */\n closeOnOverlayClick?: boolean;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`, and announced ahead of the dialog's description. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n /**\n * `alertdialog` for a dialog interrupting the user over a consequential choice — it\n * tells assistive tech to announce the body, not just the name.\n */\n role?: 'dialog' | 'alertdialog';\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** ID of the element describing what the dialog is asking. */\n 'aria-describedby'?: string;\n /**\n * Work is in flight: suppresses the pointer dismissal routes and announces the busy\n * state. Never ESC — that is the only guaranteed keyboard exit from a focus trap, so it\n * always reaches `onClose` and whether to abort is left to the caller.\n */\n busy?: boolean;\n children: React.ReactNode;\n}\n\nconst Modal: React.FC<ModalProps> = ({\n open,\n onClose,\n title,\n subtitle,\n icon,\n headerExtra,\n titleAdornment,\n tray,\n headerDivider = false,\n footer,\n size = 'medium',\n showCloseButton = true,\n closeOnOverlayClick = true,\n bodyPadding = true,\n callouts,\n renderLink,\n role = 'dialog',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n busy = false,\n children,\n}) => {\n const captionId = useId();\n const calloutId = useId();\n const shownCallouts = callouts?.filter((callout) => callout.message.trim()) ?? [];\n const [isVisible, setIsVisible] = useState(false);\n // Starts false even when `open` does: portals are unsupported on the server, so the\n // dialog must reach `document.body` from the client effect below and nowhere else.\n const [isMounted, setIsMounted] = useState(false);\n // Distinct from `!isVisible`, which is also false on the frame the dialog opens.\n const [isExiting, setIsExiting] = useState(false);\n const panelRef = useRef<HTMLElement | null>(null);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const busyRef = useRef(busy);\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n busyRef.current = busy;\n onCloseRef.current = onClose;\n }, [busy, onClose]);\n\n // Pointer routes only. Removing ESC too would leave a focus-trapped keyboard user no\n // exit while a request hangs (WCAG 2.1.2), so ESC always reaches onClose.\n const dismissByPointer = useCallback(() => {\n if (!busyRef.current) {\n onCloseRef.current();\n }\n }, []);\n const hostRef = useRef<HTMLDivElement | null>(null);\n // No prop of its own: it stays in the base band unless a SidePanel elevated the stack,\n // and follows it up when one has.\n const {\n ref: takePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n hostRef,\n });\n const handlePanelRef = useCallback(\n (node: HTMLElement | null) => {\n panelRef.current = node;\n takePanelRef(node);\n },\n [takePanelRef],\n );\n\n // Mounted outlives `open` by one transition, so the dialog can animate out.\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n setIsExiting(false);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n // A browser refuses `aria-hidden` on a subtree still holding focus, so the\n // attribute waits for this point — after useOverlayBehaviour's cleanup has\n // returned focus to the trigger — and blurs what is left if the trigger has gone.\n const active = getDeepActiveElement();\n if (active && panelRef.current?.contains(active)) {\n active.blur();\n }\n setIsExiting(true);\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would otherwise\n // flatten our z-index. Not promoted to the top layer, so host dialogs can cover us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('modal'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('modal')\n }\n >\n {/* The exit transition outlives `open`, so a closed-but-mounted dialog has to stop\n taking clicks and leave the a11y tree — a faded opacity does neither. */}\n <div\n className={`fixed inset-0 z-[1000] flex items-center justify-center p-[16px] ${\n open ? '' : 'pointer-events-none'\n }`}\n >\n <div\n aria-hidden=\"true\"\n onClick={closeOnOverlayClick ? dismissByPointer : undefined}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role={role}\n aria-hidden={isExiting ? 'true' : undefined}\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [\n ...shownCallouts.map((_, index) => `${calloutId}-${index}`),\n ariaDescribedBy,\n tray?.caption ? captionId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n }\n aria-busy={busy || undefined}\n tabIndex={-1}\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className={\n tray\n ? 'relative flex max-h-full w-full flex-col gap-[4px] rounded-[20px] p-[4px] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n : 'relative flex max-h-full w-full flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n }\n style={{\n maxWidth: tray\n ? `calc(${SIZE_MAX_WIDTH[size]} + 8px)`\n : SIZE_MAX_WIDTH[size],\n ...(tray\n ? {\n backgroundColor: MODAL_TRAY_BACKGROUND[tray.tone ?? 'neutral'],\n }\n : {}),\n // Inline: Tailwind scale/translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'scale(1)' : 'scale(0.96)',\n opacity: isVisible ? 1 : 0,\n transition: `transform ${TRANSITION_MS}ms ease-out, opacity ${TRANSITION_MS}ms ease-out`,\n }}\n >\n <div\n className={\n tray\n ? 'flex min-h-0 flex-col overflow-hidden rounded-[16px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] [box-shadow:var(--malachite-shadow-glow-lg,var(--malachite-shadow-xl))]'\n : 'contents'\n }\n >\n <div\n className={`flex gap-[20px] p-[20px] ${\n subtitle ? 'items-start' : 'items-center'\n } ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex h-[18px] min-w-0 flex-1 items-center gap-[12px]\">\n <div className=\"flex min-w-0 items-center gap-[8px]\">\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n <div className=\"flex min-w-0 flex-col gap-[4px]\">\n <span className=\"truncate text-[14px] font-medium leading-[1.25] tracking-[-0.14px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n {subtitle ? (\n <span className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\">\n {subtitle}\n </span>\n ) : null}\n </div>\n </div>\n {titleAdornment ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"h-[12px] w-px shrink-0 bg-[var(--malachite-card-border)]\"\n />\n <span className=\"flex min-w-0 items-center overflow-hidden\">\n {titleAdornment}\n </span>\n </>\n ) : null}\n </div>\n {headerExtra ? (\n <span className=\"flex shrink-0 items-center\">{headerExtra}</span>\n ) : null}\n {showCloseButton ? (\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={dismissByPointer}\n disabled={busy}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n ) : null}\n </div>\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${\n bodyPadding && shownCallouts.length === 0 ? 'px-[20px]' : ''\n }`}\n >\n {shownCallouts.map((callout, index) => (\n <CalloutStrip\n key={index}\n id={`${calloutId}-${index}`}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {bodyPadding ? (\n <div\n className={\n shownCallouts.length > 0\n ? 'px-[20px] pt-[18px] pb-[20px]'\n : `pb-[20px] ${headerDivider ? 'pt-[18px]' : ''}`\n }\n >\n {children}\n </div>\n ) : (\n children\n )}\n </div>\n {footer ? (\n <div\n className=\"flex min-h-[56px] shrink-0 items-center border-0 border-t border-solid border-[var(--malachite-card-border)] px-[12px]\"\n style={GLASS_SURFACE_STYLE}\n >\n {footer}\n </div>\n ) : null}\n </div>\n {tray?.caption ? (\n <div\n id={captionId}\n className=\"flex h-[48px] shrink-0 items-center justify-center px-[16px] text-[13px] tracking-[0.13px]\"\n style={{ color: MODAL_TRAY_CAPTION_COLOR[tray.tone ?? 'neutral'] }}\n >\n <span>\n {tray.caption.label ? (\n <>\n <span className=\"font-medium\">\n {tray.caption.label}\n </span>{' '}\n </>\n ) : null}\n {tray.caption.message}\n </span>\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default Modal;\nexport { Modal };\n"],"names":["SIZE_MAX_WIDTH","TRANSITION_MS","Modal","open","onClose","title","subtitle","icon","headerExtra","titleAdornment","tray","headerDivider","footer","size","showCloseButton","closeOnOverlayClick","bodyPadding","callouts","renderLink","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","busy","children","captionId","useId","calloutId","shownCallouts","callout","isVisible","setIsVisible","useState","isMounted","setIsMounted","isExiting","setIsExiting","panelRef","useRef","frameRef","unmountTimerRef","busyRef","onCloseRef","useEffect","dismissByPointer","useCallback","hostRef","takePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","handlePanelRef","node","active","getDeepActiveElement","_a","createPortal","jsx","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","_","index","MODAL_TRAY_BACKGROUND","Fragment","OVERLAY_ICON_BUTTON_CLASSES","DismissIcon","CalloutStrip","GLASS_SURFACE_STYLE","MODAL_TRAY_CAPTION_COLOR"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Modal.js","sources":["../../src/components/Modal.tsx"],"sourcesContent":["import type React from 'react';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport { getDeepActiveElement } from './activeElement';\nimport { GLASS_SURFACE_STYLE } from './glassSurface';\nimport { MODAL_TRAY_BACKGROUND, MODAL_TRAY_CAPTION_COLOR, type ModalTrayTone } from './modalTray';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport type ModalSize = 'small' | 'medium' | 'large';\n\n/** Widths keyed to the size union — a raw `width` prop would let a consumer off the scale. */\nconst SIZE_MAX_WIDTH: Record<ModalSize, string> = {\n small: '420px',\n medium: '612px',\n large: '760px',\n};\n\nconst TRANSITION_MS = 200;\n\n// A basis of its own: a `flex-1` body's zero basis would override the height in Chrome.\nconst TALL_BODY = 'flex-[0_1_auto] h-[min(600px,calc(100dvh-160px))]';\n\nexport interface ModalProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n /** Secondary line beneath the title. */\n subtitle?: React.ReactNode;\n /** Leading header slot, typically an icon or logo. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n titleAdornment?: React.ReactNode;\n tray?: { tone?: ModalTrayTone; caption?: { label?: string; message: string } };\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Pinned action row below the scrolling body. */\n footer?: React.ReactNode;\n size?: ModalSize;\n /** Default: true. */\n showCloseButton?: boolean;\n /** Default: true. Turn off for a dialog whose choice must be made deliberately. */\n closeOnOverlayClick?: boolean;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** `tall`: 600px, or the viewport less 160px, for content that sizes to its box. */\n bodyHeight?: 'auto' | 'tall';\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`, and announced ahead of the dialog's description. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n /**\n * `alertdialog` for a dialog interrupting the user over a consequential choice — it\n * tells assistive tech to announce the body, not just the name.\n */\n role?: 'dialog' | 'alertdialog';\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** ID of the element describing what the dialog is asking. */\n 'aria-describedby'?: string;\n /**\n * Work is in flight: suppresses the pointer dismissal routes and announces the busy\n * state. Never ESC — that is the only guaranteed keyboard exit from a focus trap, so it\n * always reaches `onClose` and whether to abort is left to the caller.\n */\n busy?: boolean;\n children: React.ReactNode;\n}\n\nconst Modal: React.FC<ModalProps> = ({\n open,\n onClose,\n title,\n subtitle,\n icon,\n headerExtra,\n titleAdornment,\n tray,\n headerDivider = false,\n footer,\n size = 'medium',\n showCloseButton = true,\n closeOnOverlayClick = true,\n bodyPadding = true,\n bodyHeight = 'auto',\n callouts,\n renderLink,\n role = 'dialog',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n busy = false,\n children,\n}) => {\n const captionId = useId();\n const calloutId = useId();\n const shownCallouts = callouts?.filter((callout) => callout.message.trim()) ?? [];\n const [isVisible, setIsVisible] = useState(false);\n // Starts false even when `open` does: portals are unsupported on the server, so the\n // dialog must reach `document.body` from the client effect below and nowhere else.\n const [isMounted, setIsMounted] = useState(false);\n // Distinct from `!isVisible`, which is also false on the frame the dialog opens.\n const [isExiting, setIsExiting] = useState(false);\n const panelRef = useRef<HTMLElement | null>(null);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const busyRef = useRef(busy);\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n busyRef.current = busy;\n onCloseRef.current = onClose;\n }, [busy, onClose]);\n\n // Pointer routes only. Removing ESC too would leave a focus-trapped keyboard user no\n // exit while a request hangs (WCAG 2.1.2), so ESC always reaches onClose.\n const dismissByPointer = useCallback(() => {\n if (!busyRef.current) {\n onCloseRef.current();\n }\n }, []);\n const hostRef = useRef<HTMLDivElement | null>(null);\n // No prop of its own: it stays in the base band unless a SidePanel elevated the stack,\n // and follows it up when one has.\n const {\n ref: takePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n hostRef,\n });\n const handlePanelRef = useCallback(\n (node: HTMLElement | null) => {\n panelRef.current = node;\n takePanelRef(node);\n },\n [takePanelRef],\n );\n\n // Mounted outlives `open` by one transition, so the dialog can animate out.\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n setIsExiting(false);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n // A browser refuses `aria-hidden` on a subtree still holding focus, so the\n // attribute waits for this point — after useOverlayBehaviour's cleanup has\n // returned focus to the trigger — and blurs what is left if the trigger has gone.\n const active = getDeepActiveElement();\n if (active && panelRef.current?.contains(active)) {\n active.blur();\n }\n setIsExiting(true);\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would otherwise\n // flatten our z-index. Not promoted to the top layer, so host dialogs can cover us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('modal'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('modal')\n }\n >\n {/* The exit transition outlives `open`, so a closed-but-mounted dialog has to stop\n taking clicks and leave the a11y tree — a faded opacity does neither. */}\n <div\n className={`fixed inset-0 z-[1000] flex items-center justify-center p-[16px] ${\n open ? '' : 'pointer-events-none'\n }`}\n >\n <div\n aria-hidden=\"true\"\n onClick={closeOnOverlayClick ? dismissByPointer : undefined}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role={role}\n aria-hidden={isExiting ? 'true' : undefined}\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [\n ...shownCallouts.map((_, index) => `${calloutId}-${index}`),\n ariaDescribedBy,\n tray?.caption ? captionId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n }\n aria-busy={busy || undefined}\n tabIndex={-1}\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className={\n tray\n ? 'relative flex max-h-full w-full flex-col gap-[4px] rounded-[20px] p-[4px] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n : 'relative flex max-h-full w-full flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n }\n style={{\n maxWidth: tray\n ? `calc(${SIZE_MAX_WIDTH[size]} + 8px)`\n : SIZE_MAX_WIDTH[size],\n ...(tray\n ? {\n backgroundColor: MODAL_TRAY_BACKGROUND[tray.tone ?? 'neutral'],\n }\n : {}),\n // Inline: Tailwind scale/translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'scale(1)' : 'scale(0.96)',\n opacity: isVisible ? 1 : 0,\n transition: `transform ${TRANSITION_MS}ms ease-out, opacity ${TRANSITION_MS}ms ease-out`,\n }}\n >\n <div\n className={\n tray\n ? 'flex min-h-0 flex-col overflow-hidden rounded-[16px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] [box-shadow:var(--malachite-shadow-glow-lg,var(--malachite-shadow-xl))]'\n : 'contents'\n }\n >\n <div\n className={`flex gap-[20px] p-[20px] ${\n subtitle ? 'items-start' : 'items-center'\n } ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex h-[18px] min-w-0 flex-1 items-center gap-[12px]\">\n <div className=\"flex min-w-0 items-center gap-[8px]\">\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n <div className=\"flex min-w-0 flex-col gap-[4px]\">\n <span className=\"truncate text-[14px] font-medium leading-[1.25] tracking-[-0.14px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n {subtitle ? (\n <span className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\">\n {subtitle}\n </span>\n ) : null}\n </div>\n </div>\n {titleAdornment ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"h-[12px] w-px shrink-0 bg-[var(--malachite-card-border)]\"\n />\n <span className=\"flex min-w-0 items-center overflow-hidden\">\n {titleAdornment}\n </span>\n </>\n ) : null}\n </div>\n {headerExtra ? (\n <span className=\"flex shrink-0 items-center\">{headerExtra}</span>\n ) : null}\n {showCloseButton ? (\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={dismissByPointer}\n disabled={busy}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n ) : null}\n </div>\n <div\n className={`min-h-0 ${bodyHeight === 'tall' ? TALL_BODY : 'flex-1'} overflow-y-auto ${\n bodyPadding && shownCallouts.length === 0 ? 'px-[20px]' : ''\n }`}\n >\n {shownCallouts.map((callout, index) => (\n <CalloutStrip\n key={index}\n id={`${calloutId}-${index}`}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {bodyPadding ? (\n <div\n className={\n shownCallouts.length > 0\n ? 'px-[20px] pt-[18px] pb-[20px]'\n : `pb-[20px] ${headerDivider ? 'pt-[18px]' : ''}`\n }\n >\n {children}\n </div>\n ) : (\n children\n )}\n </div>\n {footer ? (\n <div\n className=\"flex min-h-[56px] shrink-0 items-center border-0 border-t border-solid border-[var(--malachite-card-border)] px-[12px]\"\n style={GLASS_SURFACE_STYLE}\n >\n {footer}\n </div>\n ) : null}\n </div>\n {tray?.caption ? (\n <div\n id={captionId}\n className=\"flex h-[48px] shrink-0 items-center justify-center px-[16px] text-[13px] tracking-[0.13px]\"\n style={{ color: MODAL_TRAY_CAPTION_COLOR[tray.tone ?? 'neutral'] }}\n >\n <span>\n {tray.caption.label ? (\n <>\n <span className=\"font-medium\">\n {tray.caption.label}\n </span>{' '}\n </>\n ) : null}\n {tray.caption.message}\n </span>\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default Modal;\nexport { Modal };\n"],"names":["SIZE_MAX_WIDTH","TRANSITION_MS","TALL_BODY","Modal","open","onClose","title","subtitle","icon","headerExtra","titleAdornment","tray","headerDivider","footer","size","showCloseButton","closeOnOverlayClick","bodyPadding","bodyHeight","callouts","renderLink","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","busy","children","captionId","useId","calloutId","shownCallouts","callout","isVisible","setIsVisible","useState","isMounted","setIsMounted","isExiting","setIsExiting","panelRef","useRef","frameRef","unmountTimerRef","busyRef","onCloseRef","useEffect","dismissByPointer","useCallback","hostRef","takePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","handlePanelRef","node","active","getDeepActiveElement","_a","createPortal","jsx","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","_","index","MODAL_TRAY_BACKGROUND","Fragment","OVERLAY_ICON_BUTTON_CLASSES","DismissIcon","CalloutStrip","GLASS_SURFACE_STYLE","MODAL_TRAY_CAPTION_COLOR"],"mappings":";;;;;;;;;;;AAiBA,MAAMA,IAA4C;AAAA,EAC9C,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AACX,GAEMC,IAAgB,KAGhBC,KAAY,qDAqDZC,KAA8B,CAAC;AAAA,EACjC,MAAAC;AAAA,EACA,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,MAAAC;AAAA,EACA,aAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,MAAAC;AAAA,EACA,eAAAC,IAAgB;AAAA,EAChB,QAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,iBAAAC,IAAkB;AAAA,EAClB,qBAAAC,IAAsB;AAAA,EACtB,aAAAC,IAAc;AAAA,EACd,YAAAC,IAAa;AAAA,EACb,UAAAC;AAAA,EACA,YAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,cAAcC;AAAA,EACd,mBAAmBC;AAAA,EACnB,oBAAoBC;AAAA,EACpB,MAAAC,IAAO;AAAA,EACP,UAAAC;AACJ,MAAM;AACF,QAAMC,IAAYC,EAAA,GACZC,IAAYD,EAAA,GACZE,KAAgBX,KAAA,gBAAAA,EAAU,OAAO,CAACY,MAAYA,EAAQ,QAAQ,KAAA,OAAW,CAAA,GACzE,CAACC,GAAWC,CAAY,IAAIC,EAAS,EAAK,GAG1C,CAACC,GAAWC,CAAY,IAAIF,EAAS,EAAK,GAE1C,CAACG,GAAWC,CAAY,IAAIJ,EAAS,EAAK,GAC1CK,IAAWC,EAA2B,IAAI,GAC1CC,IAAWD,EAAsB,IAAI,GACrCE,IAAkBF,EAA6C,IAAI,GACnEG,IAAUH,EAAOf,CAAI,GACrBmB,IAAaJ,EAAOnC,CAAO;AACjC,EAAAwC,EAAU,MAAM;AACZ,IAAAF,EAAQ,UAAUlB,GAClBmB,EAAW,UAAUvC;AAAA,EACzB,GAAG,CAACoB,GAAMpB,CAAO,CAAC;AAIlB,QAAMyC,IAAmBC,EAAY,MAAM;AACvC,IAAKJ,EAAQ,WACTC,EAAW,QAAA;AAAA,EAEnB,GAAG,CAAA,CAAE,GACCI,IAAUR,EAA8B,IAAI,GAG5C;AAAA,IACF,KAAKS;AAAA,IACL,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,UAAAC;AAAA,EAAA,IACAC,GAAoB;AAAA,IACpB,MAAAjD;AAAA,IACA,SAAAC;AAAA,IACA,SAAA2C;AAAA,EAAA,CACH,GACKM,KAAiBP;AAAA,IACnB,CAACQ,MAA6B;AAC1B,MAAAhB,EAAS,UAAUgB,GACnBN,EAAaM,CAAI;AAAA,IACrB;AAAA,IACA,CAACN,CAAY;AAAA,EAAA;AA2CjB,SAvCAJ,EAAU,MAAM;;AACZ,QAAIzC;AACA,MAAAgC,EAAa,EAAI,GACjBE,EAAa,EAAK,GACdI,EAAgB,YAChB,aAAaA,EAAgB,OAAO,GACpCA,EAAgB,UAAU,OAE9BD,EAAS,UAAU,sBAAsB,MAAM;AAC3C,QAAAA,EAAS,UAAU,MACnBR,EAAa,EAAI;AAAA,MACrB,CAAC;AAAA,SACE;AAIH,YAAMuB,IAASC,GAAA;AACf,MAAID,OAAUE,IAAAnB,EAAS,YAAT,QAAAmB,EAAkB,SAASF,OACrCA,EAAO,KAAA,GAEXlB,EAAa,EAAI,GACjBL,EAAa,EAAK,GAClBS,EAAgB,UAAU,WAAW,MAAM;AACvC,QAAAA,EAAgB,UAAU,MAC1BN,EAAa,EAAK;AAAA,MACtB,GAAGnC,CAAa;AAAA,IACpB;AACA,WAAO,MAAM;AACT,MAAIwC,EAAS,YAAY,SACrB,qBAAqBA,EAAS,OAAO,GACrCA,EAAS,UAAU,OAEnBC,EAAgB,YAChB,aAAaA,EAAgB,OAAO,GACpCA,EAAgB,UAAU;AAAA,IAElC;AAAA,EACJ,GAAG,CAACtC,CAAI,CAAC,GAEJ+B,IAIEwB;AAAA;AAAA;AAAA,IAGH,gBAAAC;AAAA,MAACC;AAAA,MAAA;AAAA,QACG,SAAAb;AAAA,QACA,WAAW,EAAE,UAAU,YAAY,QAAAG,GAAA;AAAA,QACnC,WACIC,KACM,EAAE,GAAGU,EAAgB,OAAO,GAAG,CAACC,EAAqB,GAAG,GAAA,IACxDD,EAAgB,OAAO;AAAA,QAKjC,UAAA,gBAAAE;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,WAAW,oEACP5D,IAAO,KAAK,qBAChB;AAAA,YAEA,UAAA;AAAA,cAAA,gBAAAwD;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACG,eAAY;AAAA,kBACZ,SAAS5C,IAAsB8B,IAAmB;AAAA,kBAClD,WAAW,oGACPd,IAAY,gBAAgB,WAChC;AAAA,gBAAA;AAAA,cAAA;AAAA,cAEJ,gBAAAgC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACG,KAAKV;AAAA,kBACL,MAAAjC;AAAA,kBACA,eAAagB,IAAY,SAAS;AAAA,kBAClC,cAAYa,IAAY,SAAS;AAAA,kBACjC,oBACI;AAAA,oBACI,GAAGpB,EAAc,IAAI,CAACmC,GAAGC,MAAU,GAAGrC,CAAS,IAAIqC,CAAK,EAAE;AAAA,oBAC1D1C;AAAA,oBACAb,KAAA,QAAAA,EAAM,UAAUgB,IAAY;AAAA,kBAAA,EAE3B,OAAO,OAAO,EACd,KAAK,GAAG,KAAK;AAAA,kBAEtB,aAAWF,KAAQ;AAAA,kBACnB,UAAU;AAAA,kBACV,cACIF,IACM,SACCD,MAAc,OAAOhB,KAAU,WAAWA,IAAQ;AAAA,kBAE7D,mBAAiBiB;AAAA,kBACjB,WACIZ,IACM,wMACA;AAAA,kBAEV,OAAO;AAAA,oBACH,UAAUA,IACJ,QAAQX,EAAec,CAAI,CAAC,YAC5Bd,EAAec,CAAI;AAAA,oBACzB,GAAIH,IACE;AAAA,sBACI,iBAAiBwD,GAAsBxD,EAAK,QAAQ,SAAS;AAAA,oBAAA,IAEjE,CAAA;AAAA;AAAA;AAAA,oBAGN,WAAWqB,IAAY,aAAa;AAAA,oBACpC,SAASA,IAAY,IAAI;AAAA,oBACzB,YAAY,aAAa/B,CAAa,wBAAwBA,CAAa;AAAA,kBAAA;AAAA,kBAG/E,UAAA;AAAA,oBAAA,gBAAA+D;AAAA,sBAAC;AAAA,sBAAA;AAAA,wBACG,WACIrD,IACM,iOACA;AAAA,wBAGV,UAAA;AAAA,0BAAA,gBAAAqD;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACG,WAAW,4BACPzD,IAAW,gBAAgB,cAC/B,IACIK,IACM,yEACA,EACV;AAAA,8BAEA,UAAA;AAAA,gCAAA,gBAAAoD,EAAC,OAAA,EAAI,WAAU,wDACX,UAAA;AAAA,kCAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,uCACV,UAAA;AAAA,oCAAAxD,IACG,gBAAAoD,EAAC,QAAA,EAAK,WAAU,8BAA8B,aAAK,IACnD;AAAA,oCACJ,gBAAAI,EAAC,OAAA,EAAI,WAAU,mCACX,UAAA;AAAA,sCAAA,gBAAAJ,EAAC,QAAA,EAAK,WAAU,2GACX,UAAAtD,GACL;AAAA,sCACCC,IACG,gBAAAqD,EAAC,QAAA,EAAK,WAAU,oEACX,aACL,IACA;AAAA,oCAAA,EAAA,CACR;AAAA,kCAAA,GACJ;AAAA,kCACClD,IACG,gBAAAsD,EAAAI,GAAA,EACI,UAAA;AAAA,oCAAA,gBAAAR;AAAA,sCAAC;AAAA,sCAAA;AAAA,wCACG,eAAY;AAAA,wCACZ,WAAU;AAAA,sCAAA;AAAA,oCAAA;AAAA,oCAEd,gBAAAA,EAAC,QAAA,EAAK,WAAU,6CACX,UAAAlD,EAAA,CACL;AAAA,kCAAA,EAAA,CACJ,IACA;AAAA,gCAAA,GACR;AAAA,gCACCD,IACG,gBAAAmD,EAAC,QAAA,EAAK,WAAU,8BAA8B,aAAY,IAC1D;AAAA,gCACH7C,IACG,gBAAA6C;AAAA,kCAAC;AAAA,kCAAA;AAAA,oCACG,MAAK;AAAA,oCACL,cAAW;AAAA,oCACX,SAASd;AAAA,oCACT,UAAUrB;AAAA,oCACV,WAAW4C;AAAA,oCAEX,UAAA,gBAAAT,EAACU,IAAA,EAAY,MAAM,GAAA,CAAI;AAAA,kCAAA;AAAA,gCAAA,IAE3B;AAAA,8BAAA;AAAA,4BAAA;AAAA,0BAAA;AAAA,0BAER,gBAAAN;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACG,WAAW,WAAW9C,MAAe,SAAShB,KAAY,QAAQ,oBAC9De,KAAea,EAAc,WAAW,IAAI,cAAc,EAC9D;AAAA,8BAEC,UAAA;AAAA,gCAAAA,EAAc,IAAI,CAACC,GAASmC,MACzB,gBAAAN;AAAA,kCAACW;AAAA,kCAAA;AAAA,oCAEG,IAAI,GAAG1C,CAAS,IAAIqC,CAAK;AAAA,oCACzB,MAAMnC,EAAQ;AAAA,oCACd,SAASA,EAAQ;AAAA,oCACjB,YAAAX;AAAA,kCAAA;AAAA,kCAJK8C;AAAA,gCAAA,CAMZ;AAAA,gCACAjD,IACG,gBAAA2C;AAAA,kCAAC;AAAA,kCAAA;AAAA,oCACG,WACI9B,EAAc,SAAS,IACjB,kCACA,aAAalB,IAAgB,cAAc,EAAE;AAAA,oCAGtD,UAAAc;AAAA,kCAAA;AAAA,gCAAA,IAGLA;AAAA,8BAAA;AAAA,4BAAA;AAAA,0BAAA;AAAA,0BAGPb,IACG,gBAAA+C;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACG,WAAU;AAAA,8BACV,OAAOY;AAAA,8BAEN,UAAA3D;AAAA,4BAAA;AAAA,0BAAA,IAEL;AAAA,wBAAA;AAAA,sBAAA;AAAA,oBAAA;AAAA,oBAEPF,KAAA,QAAAA,EAAM,UACH,gBAAAiD;AAAA,sBAAC;AAAA,sBAAA;AAAA,wBACG,IAAIjC;AAAA,wBACJ,WAAU;AAAA,wBACV,OAAO,EAAE,OAAO8C,GAAyB9D,EAAK,QAAQ,SAAS,EAAA;AAAA,wBAE/D,4BAAC,QAAA,EACI,UAAA;AAAA,0BAAAA,EAAK,QAAQ,QACV,gBAAAqD,EAAAI,GAAA,EACI,UAAA;AAAA,4BAAA,gBAAAR,EAAC,QAAA,EAAK,WAAU,eACX,UAAAjD,EAAK,QAAQ,OAClB;AAAA,4BAAQ;AAAA,0BAAA,EAAA,CACZ,IACA;AAAA,0BACHA,EAAK,QAAQ;AAAA,wBAAA,EAAA,CAClB;AAAA,sBAAA;AAAA,oBAAA,IAEJ;AAAA,kBAAA;AAAA,gBAAA;AAAA,cAAA;AAAA,YACR;AAAA,UAAA;AAAA,QAAA;AAAA,MACJ;AAAA,IAAA;AAAA,IAEJ,SAAS;AAAA,EAAA,IA7LF;AA+Lf;"}
|