@stackone/malachite 2.43.0 → 2.45.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 +4 -4
- package/dist/components/ActionMenu.js +143 -111
- package/dist/components/ActionMenu.js.map +1 -1
- package/dist/components/NavItem.js +18 -17
- package/dist/components/NavItem.js.map +1 -1
- package/dist/components/PopoverParts.js +40 -26
- package/dist/components/PopoverParts.js.map +1 -1
- package/dist/components/SegmentedControl.js +40 -38
- package/dist/components/SegmentedControl.js.map +1 -1
- package/dist/components/Sidebar.js +132 -87
- package/dist/components/Sidebar.js.map +1 -1
- package/dist/components/TruncatedTooltip.js +24 -23
- package/dist/components/TruncatedTooltip.js.map +1 -1
- package/dist/components/actionMenuKeyboard.js +5 -1
- package/dist/components/actionMenuKeyboard.js.map +1 -1
- package/dist/components/actionMenuStyles.js +13 -11
- package/dist/components/actionMenuStyles.js.map +1 -1
- package/dist/components/linkClick.js +5 -0
- package/dist/components/linkClick.js.map +1 -0
- package/dist/components/sidebarContext.js +6 -5
- package/dist/components/sidebarContext.js.map +1 -1
- package/dist/index.d.ts +19 -2
- package/dist/index.js +300 -299
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavItem.js","sources":["../../src/components/NavItem.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport CaretRightIcon from './CustomIcons/CaretRightIcon';\nimport Tooltip from './Tooltip';\nimport { isExpanded } from './ariaState';\nimport { paintOnChild } from './asChild';\nimport { chainHandler, composeRefs, elementRef } from './composeRefs';\nimport { FOCUS_RING_INSET } from './focusRing';\nimport { resolveRel } from './linkRel';\nimport { safeHref } from './safeHref';\n\ntype NativeProps = Omit<\n React.HTMLAttributes<HTMLElement>,\n 'children' | 'className' | 'style' | 'color' | 'title'\n>;\n\ninterface NavItemBaseProps extends NativeProps {\n label: React.ReactNode;\n icon?: React.ReactNode;\n active?: boolean;\n hasActiveChild?: boolean;\n collapsed?: boolean;\n variant?: 'item' | 'subitem';\n size?: 'medium' | 'small';\n danger?: boolean;\n trailing?: React.ReactNode;\n badge?: React.ReactNode;\n badgeLabel?: string;\n tag?: React.ReactNode;\n disclosure?: 'expanded' | 'collapsed';\n tooltip?: string;\n accessibleLabel?: string;\n className?: string;\n}\n\nexport interface NavItemLinkProps extends NavItemBaseProps {\n asChild?: false;\n href?: string;\n target?: string;\n rel?: string;\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n}\n\nexport interface NavItemAsChildProps extends NavItemBaseProps {\n asChild: true;\n children: React.ReactElement;\n href?: never;\n target?: never;\n rel?: never;\n disclosure?: never;\n}\n\nexport type NavItemProps = NavItemLinkProps | NavItemAsChildProps;\n\nconst TOOLTIP_DELAY_S = 0.2;\n\nconst ROW =\n 'group relative flex items-center box-border no-underline border-0 m-0 cursor-pointer select-none text-left font-sans font-medium text-[length:var(--malachite-font-size-small,13px)] leading-[17px] rounded-[var(--malachite-radius-xl,12px)] transition-[background-color,color] duration-200';\n\nconst SHAPE = {\n item: 'w-full min-h-[37px] gap-3 py-[10px] px-3',\n compact: 'w-full min-h-9 gap-3 py-2 px-3',\n subitem: 'w-full gap-3 py-2 pr-3 pl-9',\n collapsed: 'w-9 h-[37px] shrink-0 justify-center p-0',\n} as const;\n\nconst ACTIVE_BACKGROUND =\n 'bg-[var(--malachite-accent-background-glass,rgba(0,175,102,0.08))] supports-[color:color-mix(in_srgb,red,red)]:bg-[color-mix(in_srgb,var(--malachite-accent-background,rgb(0,175,102))_12%,transparent)]';\n\nconst TONE = {\n idle: 'bg-transparent text-[var(--malachite-text-secondary,rgb(123,123,123))] hover:bg-[var(--malachite-buttonlist-hover-background,rgb(245,245,245))] hover:text-[var(--malachite-text-primary,rgb(10,10,10))]',\n active: `${ACTIVE_BACKGROUND} text-[var(--malachite-accent-foreground,rgb(2,122,66))] hover:text-[var(--malachite-accent-foreground,rgb(2,122,66))]`,\n activeChild:\n 'bg-transparent text-[var(--malachite-accent-foreground,rgb(2,122,66))] hover:bg-[var(--malachite-buttonlist-hover-background,rgb(245,245,245))] hover:text-[var(--malachite-accent-foreground,rgb(2,122,66))]',\n danger: 'bg-transparent text-[var(--malachite-text-destructive,rgb(237,53,54))] hover:bg-[var(--malachite-error-background-glass,rgba(197,63,63,0.08))] hover:text-[var(--malachite-text-destructive,rgb(237,53,54))]',\n} as const;\n\nconst DOT_TONE = {\n idle: 'bg-[var(--malachite-text-placeholder,rgb(187,187,187))] group-hover:bg-[var(--malachite-text-primary,rgb(10,10,10))]',\n active: 'bg-[var(--malachite-accent-foreground,rgb(2,122,66))]',\n activeChild: 'bg-[var(--malachite-accent-foreground,rgb(2,122,66))]',\n danger: 'bg-[var(--malachite-text-destructive,rgb(237,53,54))]',\n} as const;\n\nconst ICON = 'inline-flex shrink-0 items-center justify-center size-4 [&_svg]:size-4!';\n\nconst NavItem = forwardRef<HTMLElement, NavItemProps>((props, ref) => {\n const {\n label,\n icon,\n active = false,\n hasActiveChild = false,\n collapsed = false,\n variant = 'item',\n size = 'medium',\n danger = false,\n trailing,\n badge,\n badgeLabel,\n tag,\n disclosure,\n tooltip,\n accessibleLabel,\n className = '',\n asChild,\n ...rest\n } = props;\n\n const tone = danger ? 'danger' : active ? 'active' : hasActiveChild ? 'activeChild' : 'idle';\n const isSubitem = variant === 'subitem';\n const shapeKey = variant === 'item' && size === 'small' ? 'compact' : variant;\n const shape = collapsed ? SHAPE.collapsed : SHAPE[shapeKey];\n const rowClass = `${ROW} ${shape} ${TONE[tone]} ${FOCUS_RING_INSET} ${className}`.trim();\n\n const tooltipText = collapsed\n ? (tooltip ?? (typeof label === 'string' ? label : undefined))\n : undefined;\n const collapsedName = tooltipText\n ? [tooltipText, badgeLabel].filter(Boolean).join(', ')\n : undefined;\n const ariaLabel = accessibleLabel ?? (collapsed ? collapsedName : undefined);\n const native = rest as NativeProps;\n const ariaCurrent = active ? 'page' : native['aria-current'];\n\n const content = collapsed ? (\n <>\n {icon && (\n <span aria-hidden=\"true\" className={ICON}>\n {icon}\n </span>\n )}\n {!ariaLabel && (\n <span className=\"sr-only\">\n {label}\n {badgeLabel && `, ${badgeLabel}`}\n </span>\n )}\n {badge && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute top-1 right-1 flex\"\n >\n {badge}\n </span>\n )}\n </>\n ) : (\n <>\n {isSubitem ? (\n <span\n aria-hidden=\"true\"\n className={`absolute left-[14px] top-1/2 -mt-[2px] size-1 rounded-full transition-colors duration-200 ${DOT_TONE[tone]}`}\n />\n ) : (\n icon && (\n <span aria-hidden=\"true\" className={ICON}>\n {icon}\n </span>\n )\n )}\n <span className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"min-w-0 truncate\">{label}</span>\n {tag && <span className=\"flex shrink-0\">{tag}</span>}\n </span>\n {badge && (\n <span aria-hidden={badgeLabel ? true : undefined} className=\"flex shrink-0\">\n {badge}\n </span>\n )}\n {badge && badgeLabel && !ariaLabel && <span className=\"sr-only\">{badgeLabel}</span>}\n {trailing && <span className=\"flex shrink-0 items-center\">{trailing}</span>}\n {disclosure && (\n <span\n aria-hidden=\"true\"\n className=\"flex shrink-0 items-center\"\n style={{\n transform: disclosure === 'expanded' ? 'rotate(90deg)' : undefined,\n transition: 'transform 0.2s',\n }}\n >\n <CaretRightIcon size={14} />\n </span>\n )}\n </>\n );\n\n let element: React.ReactElement;\n if (asChild) {\n const { children, ...childNative } = rest as NativeProps & {\n children: React.ReactElement<Record<string, unknown>>;\n };\n const painted = paintOnChild(children, rowClass, undefined, content);\n const childProps = children.props;\n const chained = Object.fromEntries(\n Object.entries(childNative).map(([key, value]) =>\n key.startsWith('on') &&\n typeof value === 'function' &&\n typeof childProps[key] === 'function'\n ? [\n key,\n chainHandler(\n childProps[key] as (event: React.SyntheticEvent) => void,\n value as (event: React.SyntheticEvent) => void,\n ),\n ]\n : [key, value],\n ),\n );\n element = React.cloneElement(painted, {\n ...chained,\n ref: composeRefs(elementRef<HTMLElement>(children), ref),\n 'aria-current': ariaCurrent ?? childProps['aria-current'],\n 'aria-label': ariaLabel ?? childNative['aria-label'] ?? childProps['aria-label'],\n } as Record<string, unknown>);\n } else {\n const {\n href: rawHref,\n target,\n rel,\n ...linkNative\n } = rest as NativeProps & Pick<NavItemLinkProps, 'href' | 'target' | 'rel'>;\n const href = safeHref(rawHref);\n element =\n href !== undefined ? (\n <a\n {...linkNative}\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolveRel(rel, target)}\n aria-current={ariaCurrent}\n aria-label={ariaLabel ?? linkNative['aria-label']}\n className={rowClass}\n >\n {content}\n </a>\n ) : (\n <button\n {...linkNative}\n ref={ref as React.Ref<HTMLButtonElement>}\n type=\"button\"\n aria-expanded={\n linkNative['aria-expanded'] ??\n (disclosure ? disclosure === 'expanded' : undefined)\n }\n aria-label={ariaLabel ?? linkNative['aria-label']}\n className={rowClass}\n >\n {content}\n </button>\n );\n }\n\n const popupOpen = isExpanded(native['aria-expanded']);\n\n return tooltipText ? (\n <Tooltip\n text={tooltipText}\n placement=\"right\"\n delay={TOOLTIP_DELAY_S}\n describesTrigger={false}\n suppressed={popupOpen}\n >\n {element}\n </Tooltip>\n ) : (\n element\n );\n});\n\nNavItem.displayName = 'NavItem';\n\nexport { NavItem };\nexport default NavItem;\n"],"names":["TOOLTIP_DELAY_S","ROW","SHAPE","ACTIVE_BACKGROUND","TONE","DOT_TONE","ICON","NavItem","forwardRef","props","ref","label","icon","active","hasActiveChild","collapsed","variant","size","danger","trailing","badge","badgeLabel","tag","disclosure","tooltip","accessibleLabel","className","asChild","rest","tone","isSubitem","shape","rowClass","FOCUS_RING_INSET","tooltipText","collapsedName","ariaLabel","native","ariaCurrent","content","jsxs","Fragment","jsx","CaretRightIcon","element","children","childNative","painted","paintOnChild","childProps","chained","key","value","chainHandler","React","composeRefs","elementRef","rawHref","target","rel","linkNative","href","safeHref","resolveRel","popupOpen","isExpanded","Tooltip"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NavItem.js","sources":["../../src/components/NavItem.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport CaretRightIcon from './CustomIcons/CaretRightIcon';\nimport Tooltip from './Tooltip';\nimport { isExpanded } from './ariaState';\nimport { paintOnChild } from './asChild';\nimport { chainHandler, composeRefs, elementRef } from './composeRefs';\nimport { FOCUS_RING_INSET } from './focusRing';\nimport { resolveRel } from './linkRel';\nimport { safeHref } from './safeHref';\nimport { SIDEBAR_TOOLTIP_DELAY_S } from './sidebarContext';\n\ntype NativeProps = Omit<\n React.HTMLAttributes<HTMLElement>,\n 'children' | 'className' | 'style' | 'color' | 'title'\n>;\n\ninterface NavItemBaseProps extends NativeProps {\n label: React.ReactNode;\n icon?: React.ReactNode;\n active?: boolean;\n hasActiveChild?: boolean;\n collapsed?: boolean;\n variant?: 'item' | 'subitem';\n size?: 'medium' | 'small';\n danger?: boolean;\n trailing?: React.ReactNode;\n badge?: React.ReactNode;\n badgeLabel?: string;\n tag?: React.ReactNode;\n disclosure?: 'expanded' | 'collapsed';\n tooltip?: string;\n accessibleLabel?: string;\n className?: string;\n}\n\nexport interface NavItemLinkProps extends NavItemBaseProps {\n asChild?: false;\n href?: string;\n target?: string;\n rel?: string;\n onClick?: (event: React.MouseEvent<HTMLElement>) => void;\n}\n\nexport interface NavItemAsChildProps extends NavItemBaseProps {\n asChild: true;\n children: React.ReactElement;\n href?: never;\n target?: never;\n rel?: never;\n disclosure?: never;\n}\n\nexport type NavItemProps = NavItemLinkProps | NavItemAsChildProps;\n\nconst ROW =\n 'group relative flex items-center box-border no-underline border-0 m-0 cursor-pointer select-none text-left font-sans font-medium text-[length:var(--malachite-font-size-small,13px)] leading-[17px] rounded-[var(--malachite-radius-xl,12px)] transition-[background-color,color] duration-200';\n\nconst SHAPE = {\n item: 'w-full min-h-[37px] gap-3 py-[10px] px-3',\n compact: 'w-full min-h-9 gap-3 py-2 px-3',\n subitem: 'w-full gap-3 py-2 pr-3 pl-9',\n collapsed: 'w-9 h-[37px] shrink-0 justify-center p-0',\n} as const;\n\nconst ACTIVE_BACKGROUND =\n 'bg-[var(--malachite-accent-background-glass,rgba(0,175,102,0.08))] supports-[color:color-mix(in_srgb,red,red)]:bg-[color-mix(in_srgb,var(--malachite-accent-background,rgb(0,175,102))_12%,transparent)]';\n\nconst TONE = {\n idle: 'bg-transparent text-[var(--malachite-text-secondary,rgb(123,123,123))] hover:bg-[var(--malachite-buttonlist-hover-background,rgb(245,245,245))] hover:text-[var(--malachite-text-primary,rgb(10,10,10))]',\n active: `${ACTIVE_BACKGROUND} text-[var(--malachite-accent-foreground,rgb(2,122,66))] hover:text-[var(--malachite-accent-foreground,rgb(2,122,66))]`,\n activeChild:\n 'bg-transparent text-[var(--malachite-accent-foreground,rgb(2,122,66))] hover:bg-[var(--malachite-buttonlist-hover-background,rgb(245,245,245))] hover:text-[var(--malachite-accent-foreground,rgb(2,122,66))]',\n danger: 'bg-transparent text-[var(--malachite-text-destructive,rgb(237,53,54))] hover:bg-[var(--malachite-error-background-glass,rgba(197,63,63,0.08))] hover:text-[var(--malachite-text-destructive,rgb(237,53,54))]',\n} as const;\n\nconst DOT_TONE = {\n idle: 'bg-[var(--malachite-text-placeholder,rgb(187,187,187))] group-hover:bg-[var(--malachite-text-primary,rgb(10,10,10))]',\n active: 'bg-[var(--malachite-accent-foreground,rgb(2,122,66))]',\n activeChild: 'bg-[var(--malachite-accent-foreground,rgb(2,122,66))]',\n danger: 'bg-[var(--malachite-text-destructive,rgb(237,53,54))]',\n} as const;\n\nconst ICON = 'inline-flex shrink-0 items-center justify-center size-4 [&_svg]:size-4!';\n\nconst NavItem = forwardRef<HTMLElement, NavItemProps>((props, ref) => {\n const {\n label,\n icon,\n active = false,\n hasActiveChild = false,\n collapsed = false,\n variant = 'item',\n size = 'medium',\n danger = false,\n trailing,\n badge,\n badgeLabel,\n tag,\n disclosure,\n tooltip,\n accessibleLabel,\n className = '',\n asChild,\n ...rest\n } = props;\n\n const tone = danger ? 'danger' : active ? 'active' : hasActiveChild ? 'activeChild' : 'idle';\n const isSubitem = variant === 'subitem';\n const shapeKey = variant === 'item' && size === 'small' ? 'compact' : variant;\n const shape = collapsed ? SHAPE.collapsed : SHAPE[shapeKey];\n const rowClass = `${ROW} ${shape} ${TONE[tone]} ${FOCUS_RING_INSET} ${className}`.trim();\n\n const tooltipText = collapsed\n ? (tooltip ?? (typeof label === 'string' ? label : undefined))\n : undefined;\n const collapsedName = tooltipText\n ? [tooltipText, badgeLabel].filter(Boolean).join(', ')\n : undefined;\n const ariaLabel = accessibleLabel ?? (collapsed ? collapsedName : undefined);\n const native = rest as NativeProps;\n const ariaCurrent = active ? 'page' : native['aria-current'];\n\n const content = collapsed ? (\n <>\n {icon && (\n <span aria-hidden=\"true\" className={ICON}>\n {icon}\n </span>\n )}\n {!ariaLabel && (\n <span className=\"sr-only\">\n {label}\n {badgeLabel && `, ${badgeLabel}`}\n </span>\n )}\n {badge && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute top-1 right-1 flex\"\n >\n {badge}\n </span>\n )}\n </>\n ) : (\n <>\n {isSubitem ? (\n <span\n aria-hidden=\"true\"\n className={`absolute left-[14px] top-1/2 -mt-[2px] size-1 rounded-full transition-colors duration-200 ${DOT_TONE[tone]}`}\n />\n ) : (\n icon && (\n <span aria-hidden=\"true\" className={ICON}>\n {icon}\n </span>\n )\n )}\n <span className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"min-w-0 truncate\">{label}</span>\n {tag && <span className=\"flex shrink-0\">{tag}</span>}\n </span>\n {badge && (\n <span aria-hidden={badgeLabel ? true : undefined} className=\"flex shrink-0\">\n {badge}\n </span>\n )}\n {badge && badgeLabel && !ariaLabel && <span className=\"sr-only\">{badgeLabel}</span>}\n {trailing && <span className=\"flex shrink-0 items-center\">{trailing}</span>}\n {disclosure && (\n <span\n aria-hidden=\"true\"\n className=\"flex shrink-0 items-center\"\n style={{\n transform: disclosure === 'expanded' ? 'rotate(90deg)' : undefined,\n transition: 'transform 0.2s',\n }}\n >\n <CaretRightIcon size={14} />\n </span>\n )}\n </>\n );\n\n let element: React.ReactElement;\n if (asChild) {\n const { children, ...childNative } = rest as NativeProps & {\n children: React.ReactElement<Record<string, unknown>>;\n };\n const painted = paintOnChild(children, rowClass, undefined, content);\n const childProps = children.props;\n const chained = Object.fromEntries(\n Object.entries(childNative).map(([key, value]) =>\n key.startsWith('on') &&\n typeof value === 'function' &&\n typeof childProps[key] === 'function'\n ? [\n key,\n chainHandler(\n childProps[key] as (event: React.SyntheticEvent) => void,\n value as (event: React.SyntheticEvent) => void,\n ),\n ]\n : [key, value],\n ),\n );\n element = React.cloneElement(painted, {\n ...chained,\n ref: composeRefs(elementRef<HTMLElement>(children), ref),\n 'aria-current': ariaCurrent ?? childProps['aria-current'],\n 'aria-label': ariaLabel ?? childNative['aria-label'] ?? childProps['aria-label'],\n } as Record<string, unknown>);\n } else {\n const {\n href: rawHref,\n target,\n rel,\n ...linkNative\n } = rest as NativeProps & Pick<NavItemLinkProps, 'href' | 'target' | 'rel'>;\n const href = safeHref(rawHref);\n element =\n href !== undefined ? (\n <a\n {...linkNative}\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolveRel(rel, target)}\n aria-current={ariaCurrent}\n aria-label={ariaLabel ?? linkNative['aria-label']}\n className={rowClass}\n >\n {content}\n </a>\n ) : (\n <button\n {...linkNative}\n ref={ref as React.Ref<HTMLButtonElement>}\n type=\"button\"\n aria-expanded={\n linkNative['aria-expanded'] ??\n (disclosure ? disclosure === 'expanded' : undefined)\n }\n aria-label={ariaLabel ?? linkNative['aria-label']}\n className={rowClass}\n >\n {content}\n </button>\n );\n }\n\n const popupOpen = isExpanded(native['aria-expanded']);\n\n return tooltipText ? (\n <Tooltip\n text={tooltipText}\n placement=\"right\"\n delay={SIDEBAR_TOOLTIP_DELAY_S}\n describesTrigger={false}\n suppressed={popupOpen}\n >\n {element}\n </Tooltip>\n ) : (\n element\n );\n});\n\nNavItem.displayName = 'NavItem';\n\nexport { NavItem };\nexport default NavItem;\n"],"names":["ROW","SHAPE","ACTIVE_BACKGROUND","TONE","DOT_TONE","ICON","NavItem","forwardRef","props","ref","label","icon","active","hasActiveChild","collapsed","variant","size","danger","trailing","badge","badgeLabel","tag","disclosure","tooltip","accessibleLabel","className","asChild","rest","tone","isSubitem","shape","rowClass","FOCUS_RING_INSET","tooltipText","collapsedName","ariaLabel","native","ariaCurrent","content","jsxs","Fragment","jsx","CaretRightIcon","element","children","childNative","painted","paintOnChild","childProps","chained","key","value","chainHandler","React","composeRefs","elementRef","rawHref","target","rel","linkNative","href","safeHref","resolveRel","popupOpen","isExpanded","Tooltip","SIDEBAR_TOOLTIP_DELAY_S"],"mappings":";;;;;;;;;;;AAsDA,MAAMA,KACF,kSAEEC,IAAQ;AAAA,EACV,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AACf,GAEMC,KACF,4MAEEC,KAAO;AAAA,EACT,MAAM;AAAA,EACN,QAAQ,GAAGD,EAAiB;AAAA,EAC5B,aACI;AAAA,EACJ,QAAQ;AACZ,GAEME,KAAW;AAAA,EACb,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,QAAQ;AACZ,GAEMC,IAAO,2EAEPC,KAAUC,EAAsC,CAACC,GAAOC,MAAQ;AAClE,QAAM;AAAA,IACF,OAAAC;AAAA,IACA,MAAAC;AAAA,IACA,QAAAC,IAAS;AAAA,IACT,gBAAAC,IAAiB;AAAA,IACjB,WAAAC,IAAY;AAAA,IACZ,SAAAC,IAAU;AAAA,IACV,MAAAC,IAAO;AAAA,IACP,QAAAC,IAAS;AAAA,IACT,UAAAC;AAAA,IACA,OAAAC;AAAA,IACA,YAAAC;AAAA,IACA,KAAAC;AAAA,IACA,YAAAC;AAAA,IACA,SAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,WAAAC,IAAY;AAAA,IACZ,SAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,IACHnB,GAEEoB,IAAOX,IAAS,WAAWL,IAAS,WAAWC,IAAiB,gBAAgB,QAChFgB,IAAYd,MAAY,WAExBe,IAAQhB,IAAYb,EAAM,YAAYA,EAD3Bc,MAAY,UAAUC,MAAS,UAAU,YAAYD,CACZ,GACpDgB,IAAW,GAAG/B,EAAG,IAAI8B,CAAK,IAAI3B,GAAKyB,CAAI,CAAC,IAAII,CAAgB,IAAIP,CAAS,GAAG,KAAA,GAE5EQ,IAAcnB,IACbS,MAAY,OAAOb,KAAU,WAAWA,IAAQ,UACjD,QACAwB,IAAgBD,IAChB,CAACA,GAAab,CAAU,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,IACnD,QACAe,IAAYX,MAAoBV,IAAYoB,IAAgB,SAC5DE,IAAST,GACTU,IAAczB,IAAS,SAASwB,EAAO,cAAc,GAErDE,IAAUxB,IACZ,gBAAAyB,EAAAC,GAAA,EACK,UAAA;AAAA,IAAA7B,uBACI,QAAA,EAAK,eAAY,QAAO,WAAWN,GAC/B,UAAAM,GACL;AAAA,IAEH,CAACwB,KACE,gBAAAI,EAAC,QAAA,EAAK,WAAU,WACX,UAAA;AAAA,MAAA7B;AAAA,MACAU,KAAc,KAAKA,CAAU;AAAA,IAAA,GAClC;AAAA,IAEHD,KACG,gBAAAsB;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,eAAY;AAAA,QACZ,WAAU;AAAA,QAET,UAAAtB;AAAA,MAAA;AAAA,IAAA;AAAA,EACL,EAAA,CAER,IAEA,gBAAAoB,EAAAC,GAAA,EACK,UAAA;AAAA,IAAAX,IACG,gBAAAY;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,eAAY;AAAA,QACZ,WAAW,6FAA6FrC,GAASwB,CAAI,CAAC;AAAA,MAAA;AAAA,IAAA,IAG1HjB,KACI,gBAAA8B,EAAC,QAAA,EAAK,eAAY,QAAO,WAAWpC,GAC/B,UAAAM,GACL;AAAA,IAGR,gBAAA4B,EAAC,QAAA,EAAK,WAAU,0CACZ,UAAA;AAAA,MAAA,gBAAAE,EAAC,QAAA,EAAK,WAAU,oBAAoB,UAAA/B,GAAM;AAAA,MACzCW,KAAO,gBAAAoB,EAAC,QAAA,EAAK,WAAU,iBAAiB,UAAApB,EAAA,CAAI;AAAA,IAAA,GACjD;AAAA,IACCF,uBACI,QAAA,EAAK,eAAaC,IAAa,KAAO,QAAW,WAAU,iBACvD,UAAAD,EAAA,CACL;AAAA,IAEHA,KAASC,KAAc,CAACe,uBAAc,QAAA,EAAK,WAAU,WAAW,UAAAf,GAAW;AAAA,IAC3EF,KAAY,gBAAAuB,EAAC,QAAA,EAAK,WAAU,8BAA8B,UAAAvB,GAAS;AAAA,IACnEI,KACG,gBAAAmB;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,eAAY;AAAA,QACZ,WAAU;AAAA,QACV,OAAO;AAAA,UACH,WAAWnB,MAAe,aAAa,kBAAkB;AAAA,UACzD,YAAY;AAAA,QAAA;AAAA,QAGhB,UAAA,gBAAAmB,EAACC,GAAA,EAAe,MAAM,GAAA,CAAI;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9B,GAER;AAGJ,MAAIC;AACJ,MAAIjB,GAAS;AACT,UAAM,EAAE,UAAAkB,GAAU,GAAGC,EAAA,IAAgBlB,GAG/BmB,IAAUC,EAAaH,GAAUb,GAAU,QAAWO,CAAO,GAC7DU,IAAaJ,EAAS,OACtBK,IAAU,OAAO;AAAA,MACnB,OAAO,QAAQJ,CAAW,EAAE;AAAA,QAAI,CAAC,CAACK,GAAKC,CAAK,MACxCD,EAAI,WAAW,IAAI,KACnB,OAAOC,KAAU,cACjB,OAAOH,EAAWE,CAAG,KAAM,aACrB;AAAA,UACIA;AAAA,UACAE;AAAA,YACIJ,EAAWE,CAAG;AAAA,YACdC;AAAA,UAAA;AAAA,QACJ,IAEJ,CAACD,GAAKC,CAAK;AAAA,MAAA;AAAA,IACrB;AAEJ,IAAAR,IAAUU,EAAM,aAAaP,GAAS;AAAA,MAClC,GAAGG;AAAA,MACH,KAAKK,EAAYC,EAAwBX,CAAQ,GAAGnC,CAAG;AAAA,MACvD,gBAAgB4B,KAAeW,EAAW,cAAc;AAAA,MACxD,cAAcb,KAAaU,EAAY,YAAY,KAAKG,EAAW,YAAY;AAAA,IAAA,CACvD;AAAA,EAChC,OAAO;AACH,UAAM;AAAA,MACF,MAAMQ;AAAA,MACN,QAAAC;AAAA,MACA,KAAAC;AAAA,MACA,GAAGC;AAAA,IAAA,IACHhC,GACEiC,IAAOC,GAASL,CAAO;AAC7B,IAAAb,IACIiB,MAAS,SACL,gBAAAnB;AAAA,MAAC;AAAA,MAAA;AAAA,QACI,GAAGkB;AAAA,QACJ,KAAAlD;AAAA,QACA,MAAAmD;AAAA,QACA,QAAAH;AAAA,QACA,KAAKK,GAAWJ,GAAKD,CAAM;AAAA,QAC3B,gBAAcpB;AAAA,QACd,cAAYF,KAAawB,EAAW,YAAY;AAAA,QAChD,WAAW5B;AAAA,QAEV,UAAAO;AAAA,MAAA;AAAA,IAAA,IAGL,gBAAAG;AAAA,MAAC;AAAA,MAAA;AAAA,QACI,GAAGkB;AAAA,QACJ,KAAAlD;AAAA,QACA,MAAK;AAAA,QACL,iBACIkD,EAAW,eAAe,MACzBrC,IAAaA,MAAe,aAAa;AAAA,QAE9C,cAAYa,KAAawB,EAAW,YAAY;AAAA,QAChD,WAAW5B;AAAA,QAEV,UAAAO;AAAA,MAAA;AAAA,IAAA;AAAA,EAGjB;AAEA,QAAMyB,IAAYC,EAAW5B,EAAO,eAAe,CAAC;AAEpD,SAAOH,IACH,gBAAAQ;AAAA,IAACwB;AAAA,IAAA;AAAA,MACG,MAAMhC;AAAA,MACN,WAAU;AAAA,MACV,OAAOiC;AAAA,MACP,kBAAkB;AAAA,MAClB,YAAYH;AAAA,MAEX,UAAApB;AAAA,IAAA;AAAA,EAAA,IAGLA;AAER,CAAC;AAEDrC,GAAQ,cAAc;"}
|
|
@@ -1,46 +1,60 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { forwardRef as
|
|
3
|
-
import
|
|
4
|
-
import { BORDER as
|
|
5
|
-
import { FOCUS_RING_INSET as
|
|
6
|
-
|
|
1
|
+
import { jsx as r, jsxs as d } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as x } from "react";
|
|
3
|
+
import v from "./Tooltip.js";
|
|
4
|
+
import { BORDER as m, NO_SCROLLBAR as f } from "./chromeStyles.js";
|
|
5
|
+
import { FOCUS_RING_INSET as b } from "./focusRing.js";
|
|
6
|
+
import { isPlainClick as h } from "./linkClick.js";
|
|
7
|
+
import { safeHref as u } from "./safeHref.js";
|
|
8
|
+
const w = ({ title: e, action: t }) => /* @__PURE__ */ d(
|
|
7
9
|
"div",
|
|
8
10
|
{
|
|
9
|
-
className: `flex items-center justify-between border-0 border-b border-solid ${
|
|
11
|
+
className: `flex items-center justify-between border-0 border-b border-solid ${m} px-5 py-4 text-[12px] leading-[15px] font-normal text-[var(--malachite-text-dimmed,rgb(161,161,161))]`,
|
|
10
12
|
children: [
|
|
11
|
-
/* @__PURE__ */
|
|
12
|
-
|
|
13
|
+
/* @__PURE__ */ r("span", { children: e }),
|
|
14
|
+
t
|
|
13
15
|
]
|
|
14
16
|
}
|
|
15
|
-
),
|
|
16
|
-
({ children: e },
|
|
17
|
+
), O = ({ children: e }) => /* @__PURE__ */ r("div", { className: `flex max-h-80 flex-col gap-[2px] overflow-y-auto p-2 ${f}`, children: e }), g = x(
|
|
18
|
+
({ children: e }, t) => /* @__PURE__ */ r("div", { ref: t, className: `border-0 border-t border-solid ${m} p-2`, children: e })
|
|
17
19
|
);
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
+
g.displayName = "Popover.Section";
|
|
21
|
+
const _ = ({ children: e }) => /* @__PURE__ */ r("div", { className: "px-3 py-2 text-[length:var(--malachite-font-size-small,13px)] text-[var(--malachite-text-dimmed,rgb(161,161,161))] select-none", children: e }), z = ({
|
|
20
22
|
icon: e,
|
|
21
|
-
"aria-label":
|
|
22
|
-
onClick:
|
|
23
|
+
"aria-label": t,
|
|
24
|
+
onClick: a,
|
|
25
|
+
href: p,
|
|
23
26
|
disabled: o = !1,
|
|
24
|
-
tooltip:
|
|
27
|
+
tooltip: i
|
|
25
28
|
}) => {
|
|
26
|
-
const
|
|
29
|
+
const l = o ? void 0 : u(p), n = `m-0 inline-flex size-5 items-center justify-center rounded-[var(--malachite-radius-sm,4px)] border-0 bg-transparent p-0 transition-colors duration-150 [&_svg]:size-3! ${o ? "cursor-not-allowed text-[var(--malachite-text-placeholder,rgb(187,187,187))] opacity-50" : "cursor-pointer text-[var(--malachite-text-placeholder,rgb(187,187,187))] hover:bg-[var(--malachite-pillbutton-background,rgb(241,241,241))] hover:text-[var(--malachite-text-secondary,rgb(123,123,123))]"} ${b}`, s = l !== void 0 ? /* @__PURE__ */ r(
|
|
30
|
+
"a",
|
|
31
|
+
{
|
|
32
|
+
href: l,
|
|
33
|
+
"aria-label": t,
|
|
34
|
+
onClick: (c) => {
|
|
35
|
+
h(c) && a && (c.preventDefault(), a());
|
|
36
|
+
},
|
|
37
|
+
className: `no-underline ${n}`,
|
|
38
|
+
children: e
|
|
39
|
+
}
|
|
40
|
+
) : /* @__PURE__ */ r(
|
|
27
41
|
"button",
|
|
28
42
|
{
|
|
29
43
|
type: "button",
|
|
30
|
-
"aria-label":
|
|
44
|
+
"aria-label": t,
|
|
31
45
|
"aria-disabled": o || void 0,
|
|
32
|
-
onClick: o ? void 0 :
|
|
33
|
-
className:
|
|
46
|
+
onClick: o ? void 0 : a,
|
|
47
|
+
className: n,
|
|
34
48
|
children: e
|
|
35
49
|
}
|
|
36
50
|
);
|
|
37
|
-
return
|
|
51
|
+
return i ? /* @__PURE__ */ r(v, { text: i, children: s }) : s;
|
|
38
52
|
};
|
|
39
53
|
export {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
z as PopoverAction,
|
|
55
|
+
_ as PopoverEmpty,
|
|
56
|
+
w as PopoverHeader,
|
|
57
|
+
O as PopoverList,
|
|
58
|
+
g as PopoverSection
|
|
45
59
|
};
|
|
46
60
|
//# sourceMappingURL=PopoverParts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PopoverParts.js","sources":["../../src/components/PopoverParts.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { BORDER, NO_SCROLLBAR } from './chromeStyles';\nimport { FOCUS_RING_INSET } from './focusRing';\n\nexport interface PopoverHeaderProps {\n title: string;\n action?: React.ReactNode;\n}\n\nexport const PopoverHeader: React.FC<PopoverHeaderProps> = ({ title, action }) => (\n <div\n className={`flex items-center justify-between border-0 border-b border-solid ${BORDER} px-5 py-4 text-[12px] leading-[15px] font-normal text-[var(--malachite-text-dimmed,rgb(161,161,161))]`}\n >\n <span>{title}</span>\n {action}\n </div>\n);\n\nexport const PopoverList: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <div className={`flex max-h-80 flex-col gap-[2px] overflow-y-auto p-2 ${NO_SCROLLBAR}`}>\n {children}\n </div>\n);\n\nexport const PopoverSection = forwardRef<HTMLDivElement, { children: React.ReactNode }>(\n ({ children }, ref) => (\n <div ref={ref} className={`border-0 border-t border-solid ${BORDER} p-2`}>\n {children}\n </div>\n ),\n);\nPopoverSection.displayName = 'Popover.Section';\n\nexport const PopoverEmpty: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <div className=\"px-3 py-2 text-[length:var(--malachite-font-size-small,13px)] text-[var(--malachite-text-dimmed,rgb(161,161,161))] select-none\">\n {children}\n </div>\n);\n\nexport interface PopoverActionProps {\n icon: React.ReactNode;\n 'aria-label': string;\n onClick?: () => void;\n disabled?: boolean;\n tooltip?: string;\n}\n\nexport const PopoverAction: React.FC<PopoverActionProps> = ({\n icon,\n 'aria-label': ariaLabel,\n onClick,\n disabled = false,\n tooltip,\n}) => {\n const
|
|
1
|
+
{"version":3,"file":"PopoverParts.js","sources":["../../src/components/PopoverParts.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { BORDER, NO_SCROLLBAR } from './chromeStyles';\nimport { FOCUS_RING_INSET } from './focusRing';\nimport { isPlainClick } from './linkClick';\nimport { safeHref } from './safeHref';\n\nexport interface PopoverHeaderProps {\n title: string;\n action?: React.ReactNode;\n}\n\nexport const PopoverHeader: React.FC<PopoverHeaderProps> = ({ title, action }) => (\n <div\n className={`flex items-center justify-between border-0 border-b border-solid ${BORDER} px-5 py-4 text-[12px] leading-[15px] font-normal text-[var(--malachite-text-dimmed,rgb(161,161,161))]`}\n >\n <span>{title}</span>\n {action}\n </div>\n);\n\nexport const PopoverList: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <div className={`flex max-h-80 flex-col gap-[2px] overflow-y-auto p-2 ${NO_SCROLLBAR}`}>\n {children}\n </div>\n);\n\nexport const PopoverSection = forwardRef<HTMLDivElement, { children: React.ReactNode }>(\n ({ children }, ref) => (\n <div ref={ref} className={`border-0 border-t border-solid ${BORDER} p-2`}>\n {children}\n </div>\n ),\n);\nPopoverSection.displayName = 'Popover.Section';\n\nexport const PopoverEmpty: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <div className=\"px-3 py-2 text-[length:var(--malachite-font-size-small,13px)] text-[var(--malachite-text-dimmed,rgb(161,161,161))] select-none\">\n {children}\n </div>\n);\n\nexport interface PopoverActionProps {\n icon: React.ReactNode;\n 'aria-label': string;\n onClick?: () => void;\n /** Renders a link; a plain click runs `onClick` instead of following it, when one is given. */\n href?: string;\n disabled?: boolean;\n tooltip?: string;\n}\n\nexport const PopoverAction: React.FC<PopoverActionProps> = ({\n icon,\n 'aria-label': ariaLabel,\n onClick,\n href: rawHref,\n disabled = false,\n tooltip,\n}) => {\n const href = disabled ? undefined : safeHref(rawHref);\n const className = `m-0 inline-flex size-5 items-center justify-center rounded-[var(--malachite-radius-sm,4px)] border-0 bg-transparent p-0 transition-colors duration-150 [&_svg]:size-3! ${disabled ? 'cursor-not-allowed text-[var(--malachite-text-placeholder,rgb(187,187,187))] opacity-50' : `cursor-pointer text-[var(--malachite-text-placeholder,rgb(187,187,187))] hover:bg-[var(--malachite-pillbutton-background,rgb(241,241,241))] hover:text-[var(--malachite-text-secondary,rgb(123,123,123))]`} ${FOCUS_RING_INSET}`;\n const button =\n href !== undefined ? (\n <a\n href={href}\n aria-label={ariaLabel}\n onClick={(event) => {\n if (isPlainClick(event) && onClick) {\n event.preventDefault();\n onClick();\n }\n }}\n className={`no-underline ${className}`}\n >\n {icon}\n </a>\n ) : (\n <button\n type=\"button\"\n aria-label={ariaLabel}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : onClick}\n className={className}\n >\n {icon}\n </button>\n );\n return tooltip ? <Tooltip text={tooltip}>{button}</Tooltip> : button;\n};\n"],"names":["PopoverHeader","title","action","jsxs","BORDER","jsx","PopoverList","children","NO_SCROLLBAR","PopoverSection","forwardRef","ref","PopoverEmpty","PopoverAction","icon","ariaLabel","onClick","rawHref","disabled","tooltip","href","safeHref","className","FOCUS_RING_INSET","button","event","isPlainClick","Tooltip"],"mappings":";;;;;;;AAYO,MAAMA,IAA8C,CAAC,EAAE,OAAAC,GAAO,QAAAC,QACjE,gBAAAC;AAAA,EAAC;AAAA,EAAA;AAAA,IACG,WAAW,oEAAoEC,CAAM;AAAA,IAErF,UAAA;AAAA,MAAA,gBAAAC,EAAC,UAAM,UAAAJ,EAAA,CAAM;AAAA,MACZC;AAAA,IAAA;AAAA,EAAA;AACL,GAGSI,IAAuD,CAAC,EAAE,UAAAC,EAAA,MACnE,gBAAAF,EAAC,OAAA,EAAI,WAAW,wDAAwDG,CAAY,IAC/E,UAAAD,EAAA,CACL,GAGSE,IAAiBC;AAAA,EAC1B,CAAC,EAAE,UAAAH,EAAA,GAAYI,MACX,gBAAAN,EAAC,OAAA,EAAI,KAAAM,GAAU,WAAW,kCAAkCP,CAAM,QAC7D,UAAAG,EAAA,CACL;AAER;AACAE,EAAe,cAAc;AAEtB,MAAMG,IAAwD,CAAC,EAAE,UAAAL,QACpE,gBAAAF,EAAC,OAAA,EAAI,WAAU,kIACV,UAAAE,EAAA,CACL,GAaSM,IAA8C,CAAC;AAAA,EACxD,MAAAC;AAAA,EACA,cAAcC;AAAA,EACd,SAAAC;AAAA,EACA,MAAMC;AAAA,EACN,UAAAC,IAAW;AAAA,EACX,SAAAC;AACJ,MAAM;AACF,QAAMC,IAAOF,IAAW,SAAYG,EAASJ,CAAO,GAC9CK,IAAY,0KAA0KJ,IAAW,4FAA4F,2MAA2M,IAAIK,CAAgB,IAC5fC,IACFJ,MAAS,SACL,gBAAAf;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,MAAAe;AAAA,MACA,cAAYL;AAAA,MACZ,SAAS,CAACU,MAAU;AAChB,QAAIC,EAAaD,CAAK,KAAKT,MACvBS,EAAM,eAAA,GACNT,EAAA;AAAA,MAER;AAAA,MACA,WAAW,gBAAgBM,CAAS;AAAA,MAEnC,UAAAR;AAAA,IAAA;AAAA,EAAA,IAGL,gBAAAT;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,MAAK;AAAA,MACL,cAAYU;AAAA,MACZ,iBAAeG,KAAY;AAAA,MAC3B,SAASA,IAAW,SAAYF;AAAA,MAChC,WAAAM;AAAA,MAEC,UAAAR;AAAA,IAAA;AAAA,EAAA;AAGb,SAAOK,IAAU,gBAAAd,EAACsB,GAAA,EAAQ,MAAMR,GAAU,aAAO,IAAaK;AAClE;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { jsx as
|
|
1
|
+
import { jsx as u, jsxs as I } from "react/jsx-runtime";
|
|
2
2
|
import { useRef as $, useCallback as A } from "react";
|
|
3
3
|
import { FOCUS_RING as E } from "./focusRing.js";
|
|
4
|
-
const
|
|
4
|
+
const N = {
|
|
5
5
|
small: {
|
|
6
6
|
text: "text-[length:var(--malachite-font-size-small,13px)]",
|
|
7
7
|
track: "h-[var(--malachite-control-height-small,28px)]",
|
|
@@ -17,81 +17,83 @@ const D = {
|
|
|
17
17
|
track: "h-[var(--malachite-control-height-large,42px)]",
|
|
18
18
|
item: "h-full px-4"
|
|
19
19
|
}
|
|
20
|
-
},
|
|
21
|
-
options:
|
|
22
|
-
value:
|
|
20
|
+
}, D = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"]), j = "inline-flex items-center justify-center gap-1.5 rounded-[var(--malachite-radius-10,10px)] border-0 font-medium tracking-[0.13px] whitespace-nowrap transition-colors duration-150 ease-in-out", B = ({
|
|
21
|
+
options: s,
|
|
22
|
+
value: o,
|
|
23
23
|
onChange: d,
|
|
24
24
|
size: b = "medium",
|
|
25
|
-
fullWidth:
|
|
26
|
-
disabled:
|
|
25
|
+
fullWidth: x = !1,
|
|
26
|
+
disabled: c = !1,
|
|
27
27
|
"aria-label": p,
|
|
28
28
|
"aria-labelledby": k,
|
|
29
29
|
className: v = ""
|
|
30
30
|
}) => {
|
|
31
|
-
const
|
|
31
|
+
const g = $([]), m = N[b], h = s.findIndex((e) => e.value === o), w = h >= 0 && !s[h].disabled ? h : s.findIndex((e) => !e.disabled), y = A(
|
|
32
32
|
(e) => {
|
|
33
|
-
var
|
|
34
|
-
if (
|
|
33
|
+
var n;
|
|
34
|
+
if (c || !D.has(e.key))
|
|
35
35
|
return;
|
|
36
|
-
const t =
|
|
36
|
+
const t = s.reduce((l, f, S) => (f.disabled || l.push({ option: f, index: S }), l), []);
|
|
37
37
|
if (t.length === 0)
|
|
38
38
|
return;
|
|
39
39
|
e.preventDefault();
|
|
40
|
-
const
|
|
41
|
-
let
|
|
40
|
+
const r = t.findIndex(({ option: l }) => l.value === o);
|
|
41
|
+
let a;
|
|
42
42
|
if (e.key === "Home")
|
|
43
|
-
|
|
43
|
+
a = 0;
|
|
44
44
|
else if (e.key === "End")
|
|
45
|
-
|
|
45
|
+
a = t.length - 1;
|
|
46
46
|
else {
|
|
47
|
-
const
|
|
48
|
-
|
|
47
|
+
const l = e.key === "ArrowLeft" || e.key === "ArrowUp" ? -1 : 1;
|
|
48
|
+
a = ((r === -1 ? 0 : r) + l + t.length) % t.length;
|
|
49
49
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
50
|
+
const i = t[a];
|
|
51
|
+
i.option.value !== o && d(i.option.value), (n = g.current[i.index]) == null || n.focus();
|
|
52
52
|
},
|
|
53
|
-
[
|
|
53
|
+
[c, d, s, o]
|
|
54
54
|
);
|
|
55
|
-
return /* @__PURE__ */
|
|
55
|
+
return /* @__PURE__ */ u(
|
|
56
56
|
"div",
|
|
57
57
|
{
|
|
58
58
|
role: "radiogroup",
|
|
59
59
|
"aria-label": p,
|
|
60
60
|
"aria-labelledby": k,
|
|
61
|
-
"aria-disabled":
|
|
61
|
+
"aria-disabled": c || void 0,
|
|
62
62
|
onKeyDown: y,
|
|
63
63
|
className: `${// One display utility, not two: `flex` and `inline-flex` are both plain
|
|
64
64
|
// `display` utilities, so emitting both would resolve by stylesheet order
|
|
65
65
|
// (same hazard as the background above).
|
|
66
|
-
|
|
67
|
-
children:
|
|
68
|
-
const
|
|
69
|
-
let
|
|
70
|
-
return
|
|
66
|
+
x ? "flex w-full" : "inline-flex"} ${m.track} items-center gap-1 p-0.5 rounded-xl bg-[var(--malachite-segmented-control-track-background)] ${c ? "opacity-50" : ""} ${v}`,
|
|
67
|
+
children: s.map((e, t) => {
|
|
68
|
+
const r = e.value === o, a = c || e.disabled, i = e.badge !== void 0 && e.badge !== null && e.badge !== !1;
|
|
69
|
+
let n;
|
|
70
|
+
return r ? n = "bg-[var(--malachite-segmented-control-selected-background)] text-[var(--malachite-segmented-control-selected-foreground)]" : a ? n = "bg-transparent text-[var(--malachite-segmented-control-item-foreground)]" : n = "bg-transparent text-[var(--malachite-segmented-control-item-foreground)] hover:text-[var(--malachite-segmented-control-item-hover-foreground)] cursor-pointer", /* @__PURE__ */ I(
|
|
71
71
|
"button",
|
|
72
72
|
{
|
|
73
|
-
ref: (
|
|
74
|
-
|
|
73
|
+
ref: (l) => {
|
|
74
|
+
g.current[t] = l;
|
|
75
75
|
},
|
|
76
76
|
type: "button",
|
|
77
77
|
role: "radio",
|
|
78
|
-
"aria-checked":
|
|
79
|
-
disabled:
|
|
78
|
+
"aria-checked": r,
|
|
79
|
+
disabled: a,
|
|
80
80
|
tabIndex: t === w ? 0 : -1,
|
|
81
81
|
onClick: () => {
|
|
82
|
-
!
|
|
82
|
+
!a && !r && d(e.value);
|
|
83
83
|
},
|
|
84
|
-
className: `${E} ${j} ${m.text} ${m.item} ${
|
|
84
|
+
className: `${E} ${j} ${m.text} ${m.item} ${n} ${x ? "flex-1" : ""} ${e.disabled && !c ? "opacity-50 cursor-not-allowed" : ""}`,
|
|
85
85
|
style: {
|
|
86
86
|
// Inline rather than a `shadow-[...]` class: Tailwind's shadow
|
|
87
87
|
// utilities compose `--tw-shadow-*` custom properties that rely on
|
|
88
88
|
// document-level @property registration and silently don't paint
|
|
89
89
|
// inside a shadow root (same caveat as `ring-*`).
|
|
90
|
-
boxShadow:
|
|
90
|
+
boxShadow: r ? "var(--malachite-segmented-control-selected-shadow, 0px 1px 5px 0px rgba(0, 0, 0, 0.1))" : void 0
|
|
91
91
|
},
|
|
92
92
|
children: [
|
|
93
|
-
e.icon ? /* @__PURE__ */
|
|
94
|
-
e.label
|
|
93
|
+
e.icon ? /* @__PURE__ */ u("span", { className: "inline-flex items-center justify-center w-4 h-4 shrink-0", children: e.icon }) : null,
|
|
94
|
+
e.label,
|
|
95
|
+
i ? " " : null,
|
|
96
|
+
i ? /* @__PURE__ */ u("span", { className: "inline-flex items-center shrink-0", children: e.badge }) : null
|
|
95
97
|
]
|
|
96
98
|
},
|
|
97
99
|
e.value
|
|
@@ -101,7 +103,7 @@ const D = {
|
|
|
101
103
|
);
|
|
102
104
|
};
|
|
103
105
|
export {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
B as SegmentedControl,
|
|
107
|
+
B as default
|
|
106
108
|
};
|
|
107
109
|
//# sourceMappingURL=SegmentedControl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControl.js","sources":["../../src/components/SegmentedControl.tsx"],"sourcesContent":["import React, { useCallback, useRef } from 'react';\nimport { FOCUS_RING } from './focusRing';\n\ntype SegmentedControlSize = 'small' | 'medium' | 'large';\n\ninterface SegmentedControlOption<T extends string = string> {\n value: T;\n label: string;\n icon?: React.ReactNode;\n disabled?: boolean;\n}\n\ninterface SegmentedControlProps<T extends string = string> {\n options: SegmentedControlOption<T>[];\n /** The selected option's value. A value not present in `options` renders nothing selected. */\n value: T;\n onChange: (value: T) => void;\n size?: SegmentedControlSize;\n /** Stretch the track to its container and split it into equal segments. */\n fullWidth?: boolean;\n /** Disables every segment. Individual segments can also be disabled via `options`. */\n disabled?: boolean;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n}\n\n// The ladder governs a control's outer box. The height therefore sits on the track and\n// the items fill whatever the 4px inset leaves — putting it on the item instead makes a\n// `medium` segmented control 40px tall beside a 32px Dropdown.\nconst SIZE_CLASSES: Record<SegmentedControlSize, { text: string; track: string; item: string }> = {\n small: {\n text: 'text-[length:var(--malachite-font-size-small,13px)]',\n track: 'h-[var(--malachite-control-height-small,28px)]',\n item: 'h-full px-2.5',\n },\n medium: {\n text: 'text-[length:var(--malachite-font-size-small,13px)]',\n track: 'h-[var(--malachite-control-height-medium,32px)]',\n item: 'h-full px-2.5',\n },\n large: {\n text: 'text-[length:var(--malachite-font-size-15,15px)]',\n track: 'h-[var(--malachite-control-height-large,42px)]',\n item: 'h-full px-4',\n },\n};\n\n/** Arrow keys move AND select, per the WAI-ARIA radiogroup pattern. */\nconst NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);\n\n// Deliberately no `bg-*` here: each state branch below supplies exactly one\n// background-color utility. Both `bg-transparent` and `bg-[var(--…)]` are plain\n// single-class utilities in the same `@layer utilities`, so an element carrying both\n// resolves by stylesheet order — and the arbitrary value loses, silently rendering the\n// selected thumb transparent.\nconst ITEM_BASE =\n 'inline-flex items-center justify-center gap-1.5 rounded-[var(--malachite-radius-10,10px)] border-0 font-medium tracking-[0.13px] whitespace-nowrap transition-colors duration-150 ease-in-out';\n\n/**\n * A rounded track of mutually-exclusive options — the segmented \"one of these\" control.\n *\n * Owns the track background, the selected-thumb chrome and the radiogroup semantics in\n * one place, so consumers never hand-roll a track around `PillButton` (whose `default`\n * variant tokens are tuned for standalone filter pills, not for a sunken track).\n *\n * Selection is conveyed non-visually via `role=\"radio\"` + `aria-checked`, and by a\n * foreground-colour change as well as the thumb, so it does not rest on background\n * colour alone.\n */\nconst SegmentedControl = <T extends string = string>({\n options,\n value,\n onChange,\n size = 'medium',\n fullWidth = false,\n disabled = false,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className = '',\n}: SegmentedControlProps<T>) => {\n const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const sizeBlock = SIZE_CLASSES[size];\n\n const selectedIndex = options.findIndex((option) => option.value === value);\n // Roving tabindex: the group is a single tab stop. When the selected option is\n // missing or disabled, the first enabled option takes the stop so the control is\n // still reachable by keyboard.\n const selectedIsEnabled = selectedIndex >= 0 && !options[selectedIndex].disabled;\n const tabStopIndex = selectedIsEnabled\n ? selectedIndex\n : options.findIndex((option) => !option.disabled);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (disabled || !NAV_KEYS.has(event.key)) {\n return;\n }\n\n const enabled = options.reduce<\n Array<{ option: SegmentedControlOption<T>; index: number }>\n >((acc, option, index) => {\n if (!option.disabled) {\n acc.push({ option, index });\n }\n return acc;\n }, []);\n if (enabled.length === 0) {\n return;\n }\n\n // Only once we know we'll act — otherwise Home/End etc. should still scroll.\n event.preventDefault();\n\n const currentPosition = enabled.findIndex(({ option }) => option.value === value);\n let nextPosition: number;\n if (event.key === 'Home') {\n nextPosition = 0;\n } else if (event.key === 'End') {\n nextPosition = enabled.length - 1;\n } else {\n const delta = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1;\n const from = currentPosition === -1 ? 0 : currentPosition;\n nextPosition = (from + delta + enabled.length) % enabled.length;\n }\n\n const next = enabled[nextPosition];\n if (next.option.value !== value) {\n onChange(next.option.value);\n }\n itemRefs.current[next.index]?.focus();\n },\n [disabled, onChange, options, value],\n );\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-disabled={disabled || undefined}\n onKeyDown={handleKeyDown}\n className={`${\n // One display utility, not two: `flex` and `inline-flex` are both plain\n // `display` utilities, so emitting both would resolve by stylesheet order\n // (same hazard as the background above).\n fullWidth ? 'flex w-full' : 'inline-flex'\n } ${sizeBlock.track} items-center gap-1 p-0.5 rounded-xl bg-[var(--malachite-segmented-control-track-background)] ${\n disabled ? 'opacity-50' : ''\n } ${className}`}\n >\n {options.map((option, index) => {\n const isSelected = option.value === value;\n const isDisabled = disabled || option.disabled;\n\n let colorClasses: string;\n if (isSelected) {\n colorClasses =\n 'bg-[var(--malachite-segmented-control-selected-background)] text-[var(--malachite-segmented-control-selected-foreground)]';\n } else if (isDisabled) {\n colorClasses =\n 'bg-transparent text-[var(--malachite-segmented-control-item-foreground)]';\n } else {\n colorClasses =\n 'bg-transparent text-[var(--malachite-segmented-control-item-foreground)] hover:text-[var(--malachite-segmented-control-item-hover-foreground)] cursor-pointer';\n }\n\n return (\n <button\n key={option.value}\n ref={(element) => {\n itemRefs.current[index] = element;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n disabled={isDisabled}\n tabIndex={index === tabStopIndex ? 0 : -1}\n onClick={() => {\n // Radios don't toggle off on re-click.\n if (!isDisabled && !isSelected) {\n onChange(option.value);\n }\n }}\n className={`${FOCUS_RING} ${ITEM_BASE} ${sizeBlock.text} ${sizeBlock.item} ${colorClasses} ${\n fullWidth ? 'flex-1' : ''\n } ${option.disabled && !disabled ? 'opacity-50 cursor-not-allowed' : ''}`}\n style={{\n // Inline rather than a `shadow-[...]` class: Tailwind's shadow\n // utilities compose `--tw-shadow-*` custom properties that rely on\n // document-level @property registration and silently don't paint\n // inside a shadow root (same caveat as `ring-*`).\n boxShadow: isSelected\n ? 'var(--malachite-segmented-control-selected-shadow, 0px 1px 5px 0px rgba(0, 0, 0, 0.1))'\n : undefined,\n }}\n >\n {option.icon ? (\n <span className=\"inline-flex items-center justify-center w-4 h-4 shrink-0\">\n {option.icon}\n </span>\n ) : null}\n {option.label}\n </button>\n );\n })}\n </div>\n );\n};\n\nexport default SegmentedControl;\nexport { SegmentedControl };\nexport type { SegmentedControlProps, SegmentedControlOption, SegmentedControlSize };\n"],"names":["SIZE_CLASSES","NAV_KEYS","ITEM_BASE","SegmentedControl","options","value","onChange","size","fullWidth","disabled","ariaLabel","ariaLabelledBy","className","itemRefs","useRef","sizeBlock","selectedIndex","option","tabStopIndex","handleKeyDown","useCallback","event","enabled","acc","index","currentPosition","nextPosition","delta","next","_a","jsx","isSelected","isDisabled","colorClasses","jsxs","element","FOCUS_RING"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SegmentedControl.js","sources":["../../src/components/SegmentedControl.tsx"],"sourcesContent":["import React, { useCallback, useRef } from 'react';\nimport { FOCUS_RING } from './focusRing';\n\ntype SegmentedControlSize = 'small' | 'medium' | 'large';\n\ninterface SegmentedControlOption<T extends string = string> {\n value: T;\n label: string;\n icon?: React.ReactNode;\n /**\n * Small mark after the label, e.g. a compact `Tag` naming a saving. It renders inside\n * the segment's `<button>`, so it must not be interactive — no `onClick` or `onRemove`.\n */\n badge?: React.ReactNode;\n disabled?: boolean;\n}\n\ninterface SegmentedControlProps<T extends string = string> {\n options: SegmentedControlOption<T>[];\n /** The selected option's value. A value not present in `options` renders nothing selected. */\n value: T;\n onChange: (value: T) => void;\n size?: SegmentedControlSize;\n /** Stretch the track to its container and split it into equal segments. */\n fullWidth?: boolean;\n /** Disables every segment. Individual segments can also be disabled via `options`. */\n disabled?: boolean;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n}\n\n// The ladder governs a control's outer box. The height therefore sits on the track and\n// the items fill whatever the 4px inset leaves — putting it on the item instead makes a\n// `medium` segmented control 40px tall beside a 32px Dropdown.\nconst SIZE_CLASSES: Record<SegmentedControlSize, { text: string; track: string; item: string }> = {\n small: {\n text: 'text-[length:var(--malachite-font-size-small,13px)]',\n track: 'h-[var(--malachite-control-height-small,28px)]',\n item: 'h-full px-2.5',\n },\n medium: {\n text: 'text-[length:var(--malachite-font-size-small,13px)]',\n track: 'h-[var(--malachite-control-height-medium,32px)]',\n item: 'h-full px-2.5',\n },\n large: {\n text: 'text-[length:var(--malachite-font-size-15,15px)]',\n track: 'h-[var(--malachite-control-height-large,42px)]',\n item: 'h-full px-4',\n },\n};\n\n/** Arrow keys move AND select, per the WAI-ARIA radiogroup pattern. */\nconst NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);\n\n// Deliberately no `bg-*` here: each state branch below supplies exactly one\n// background-color utility. Both `bg-transparent` and `bg-[var(--…)]` are plain\n// single-class utilities in the same `@layer utilities`, so an element carrying both\n// resolves by stylesheet order — and the arbitrary value loses, silently rendering the\n// selected thumb transparent.\nconst ITEM_BASE =\n 'inline-flex items-center justify-center gap-1.5 rounded-[var(--malachite-radius-10,10px)] border-0 font-medium tracking-[0.13px] whitespace-nowrap transition-colors duration-150 ease-in-out';\n\n/**\n * A rounded track of mutually-exclusive options — the segmented \"one of these\" control.\n *\n * Owns the track background, the selected-thumb chrome and the radiogroup semantics in\n * one place, so consumers never hand-roll a track around `PillButton` (whose `default`\n * variant tokens are tuned for standalone filter pills, not for a sunken track).\n *\n * Selection is conveyed non-visually via `role=\"radio\"` + `aria-checked`, and by a\n * foreground-colour change as well as the thumb, so it does not rest on background\n * colour alone.\n */\nconst SegmentedControl = <T extends string = string>({\n options,\n value,\n onChange,\n size = 'medium',\n fullWidth = false,\n disabled = false,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className = '',\n}: SegmentedControlProps<T>) => {\n const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const sizeBlock = SIZE_CLASSES[size];\n\n const selectedIndex = options.findIndex((option) => option.value === value);\n // Roving tabindex: the group is a single tab stop. When the selected option is\n // missing or disabled, the first enabled option takes the stop so the control is\n // still reachable by keyboard.\n const selectedIsEnabled = selectedIndex >= 0 && !options[selectedIndex].disabled;\n const tabStopIndex = selectedIsEnabled\n ? selectedIndex\n : options.findIndex((option) => !option.disabled);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (disabled || !NAV_KEYS.has(event.key)) {\n return;\n }\n\n const enabled = options.reduce<\n Array<{ option: SegmentedControlOption<T>; index: number }>\n >((acc, option, index) => {\n if (!option.disabled) {\n acc.push({ option, index });\n }\n return acc;\n }, []);\n if (enabled.length === 0) {\n return;\n }\n\n // Only once we know we'll act — otherwise Home/End etc. should still scroll.\n event.preventDefault();\n\n const currentPosition = enabled.findIndex(({ option }) => option.value === value);\n let nextPosition: number;\n if (event.key === 'Home') {\n nextPosition = 0;\n } else if (event.key === 'End') {\n nextPosition = enabled.length - 1;\n } else {\n const delta = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1;\n const from = currentPosition === -1 ? 0 : currentPosition;\n nextPosition = (from + delta + enabled.length) % enabled.length;\n }\n\n const next = enabled[nextPosition];\n if (next.option.value !== value) {\n onChange(next.option.value);\n }\n itemRefs.current[next.index]?.focus();\n },\n [disabled, onChange, options, value],\n );\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-disabled={disabled || undefined}\n onKeyDown={handleKeyDown}\n className={`${\n // One display utility, not two: `flex` and `inline-flex` are both plain\n // `display` utilities, so emitting both would resolve by stylesheet order\n // (same hazard as the background above).\n fullWidth ? 'flex w-full' : 'inline-flex'\n } ${sizeBlock.track} items-center gap-1 p-0.5 rounded-xl bg-[var(--malachite-segmented-control-track-background)] ${\n disabled ? 'opacity-50' : ''\n } ${className}`}\n >\n {options.map((option, index) => {\n const isSelected = option.value === value;\n const isDisabled = disabled || option.disabled;\n const hasBadge =\n option.badge !== undefined && option.badge !== null && option.badge !== false;\n\n let colorClasses: string;\n if (isSelected) {\n colorClasses =\n 'bg-[var(--malachite-segmented-control-selected-background)] text-[var(--malachite-segmented-control-selected-foreground)]';\n } else if (isDisabled) {\n colorClasses =\n 'bg-transparent text-[var(--malachite-segmented-control-item-foreground)]';\n } else {\n colorClasses =\n 'bg-transparent text-[var(--malachite-segmented-control-item-foreground)] hover:text-[var(--malachite-segmented-control-item-hover-foreground)] cursor-pointer';\n }\n\n return (\n <button\n key={option.value}\n ref={(element) => {\n itemRefs.current[index] = element;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n disabled={isDisabled}\n tabIndex={index === tabStopIndex ? 0 : -1}\n onClick={() => {\n // Radios don't toggle off on re-click.\n if (!isDisabled && !isSelected) {\n onChange(option.value);\n }\n }}\n className={`${FOCUS_RING} ${ITEM_BASE} ${sizeBlock.text} ${sizeBlock.item} ${colorClasses} ${\n fullWidth ? 'flex-1' : ''\n } ${option.disabled && !disabled ? 'opacity-50 cursor-not-allowed' : ''}`}\n style={{\n // Inline rather than a `shadow-[...]` class: Tailwind's shadow\n // utilities compose `--tw-shadow-*` custom properties that rely on\n // document-level @property registration and silently don't paint\n // inside a shadow root (same caveat as `ring-*`).\n boxShadow: isSelected\n ? 'var(--malachite-segmented-control-selected-shadow, 0px 1px 5px 0px rgba(0, 0, 0, 0.1))'\n : undefined,\n }}\n >\n {option.icon ? (\n <span className=\"inline-flex items-center justify-center w-4 h-4 shrink-0\">\n {option.icon}\n </span>\n ) : null}\n {option.label}\n {/* The space keeps the name \"Annual Save\" rather than \"AnnualSave\";\n the flex gap already spaces the two visually. */}\n {hasBadge ? ' ' : null}\n {hasBadge ? (\n <span className=\"inline-flex items-center shrink-0\">\n {option.badge}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n );\n};\n\nexport default SegmentedControl;\nexport { SegmentedControl };\nexport type { SegmentedControlProps, SegmentedControlOption, SegmentedControlSize };\n"],"names":["SIZE_CLASSES","NAV_KEYS","ITEM_BASE","SegmentedControl","options","value","onChange","size","fullWidth","disabled","ariaLabel","ariaLabelledBy","className","itemRefs","useRef","sizeBlock","selectedIndex","option","tabStopIndex","handleKeyDown","useCallback","event","enabled","acc","index","currentPosition","nextPosition","delta","next","_a","jsx","isSelected","isDisabled","hasBadge","colorClasses","jsxs","element","FOCUS_RING"],"mappings":"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"}
|