@stackone/malachite 2.72.0 → 2.74.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 +7 -5
- package/dist/components/Alert.js +94 -119
- package/dist/components/Alert.js.map +1 -1
- package/dist/components/AvatarGroup.js +74 -0
- package/dist/components/AvatarGroup.js.map +1 -0
- package/dist/components/Button.js +79 -190
- package/dist/components/Button.js.map +1 -1
- package/dist/components/ButtonSpinner.js +36 -0
- package/dist/components/ButtonSpinner.js.map +1 -0
- package/dist/components/Flex.js +14 -13
- package/dist/components/Flex.js.map +1 -1
- package/dist/components/Sidebar.js +4 -8
- package/dist/components/Sidebar.js.map +1 -1
- package/dist/components/SplitButton.js +91 -0
- package/dist/components/SplitButton.js.map +1 -0
- package/dist/components/Surface.js +68 -64
- package/dist/components/Surface.js.map +1 -1
- package/dist/components/Typography/EllipsisText.js +18 -16
- package/dist/components/Typography/EllipsisText.js.map +1 -1
- package/dist/components/buttonStyles.js +93 -0
- package/dist/components/buttonStyles.js.map +1 -0
- package/dist/components/floatingMenuGeometry.js.map +1 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +469 -465
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const s = (a) => {
|
|
2
|
+
switch (a) {
|
|
3
|
+
// Figma has no text X-Small button, only the icon-only square — nothing to align to.
|
|
4
|
+
case "xsmall":
|
|
5
|
+
return "[--btn-px:calc(var(--spacing)*2)] px-[var(--btn-px)] py-1 text-xs";
|
|
6
|
+
case "small":
|
|
7
|
+
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";
|
|
8
|
+
case "medium":
|
|
9
|
+
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";
|
|
10
|
+
case "large":
|
|
11
|
+
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";
|
|
12
|
+
default:
|
|
13
|
+
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";
|
|
14
|
+
}
|
|
15
|
+
}, n = (a, r) => {
|
|
16
|
+
switch (a) {
|
|
17
|
+
// Only the square rounds at 8px — text buttons at this size keep their 6px corner.
|
|
18
|
+
case "xsmall":
|
|
19
|
+
return r ? "rounded-lg" : "rounded-md";
|
|
20
|
+
case "small":
|
|
21
|
+
return "rounded-[var(--malachite-radius-10,10px)]";
|
|
22
|
+
case "medium":
|
|
23
|
+
return "rounded-xl";
|
|
24
|
+
case "large":
|
|
25
|
+
return "rounded-[var(--malachite-radius-xxl,14px)]";
|
|
26
|
+
default:
|
|
27
|
+
return "rounded-[var(--malachite-radius-xxl,14px)]";
|
|
28
|
+
}
|
|
29
|
+
}, l = (a, r) => {
|
|
30
|
+
const e = r === "start";
|
|
31
|
+
switch (a) {
|
|
32
|
+
case "small":
|
|
33
|
+
case "medium":
|
|
34
|
+
return e ? "mr-[6px]" : "ml-[6px]";
|
|
35
|
+
default:
|
|
36
|
+
return e ? "mr-2" : "ml-2";
|
|
37
|
+
}
|
|
38
|
+
}, i = (a) => {
|
|
39
|
+
switch (a) {
|
|
40
|
+
case "md":
|
|
41
|
+
return "rounded-md";
|
|
42
|
+
case "lg":
|
|
43
|
+
return "rounded-lg";
|
|
44
|
+
case "xl":
|
|
45
|
+
return "rounded-xl";
|
|
46
|
+
default:
|
|
47
|
+
return "rounded-xl";
|
|
48
|
+
}
|
|
49
|
+
}, t = {
|
|
50
|
+
neutral: "focus-visible:[outline:2px_solid_var(--malachite-text-primary)]",
|
|
51
|
+
destructive: "focus-visible:[outline:2px_solid_var(--malachite-text-destructive)]",
|
|
52
|
+
success: "focus-visible:[outline:2px_solid_var(--malachite-text-success)]",
|
|
53
|
+
warning: "focus-visible:[outline:2px_solid_var(--malachite-text-warning)]",
|
|
54
|
+
error: "focus-visible:[outline:2px_solid_var(--malachite-text-error)]",
|
|
55
|
+
assistant: "focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]",
|
|
56
|
+
accent: "focus-visible:[outline:2px_solid_var(--malachite-accent-foreground)]"
|
|
57
|
+
}, u = (a, r) => {
|
|
58
|
+
const e = `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 ${r} cursor-pointer`;
|
|
59
|
+
switch (a) {
|
|
60
|
+
case "filled":
|
|
61
|
+
return `${e} ${t.neutral} btn-filled shadow-default`;
|
|
62
|
+
case "outline":
|
|
63
|
+
return `${e} ${t.neutral} btn-outline shadow-default`;
|
|
64
|
+
case "surface":
|
|
65
|
+
return `${e} ${t.neutral} btn-surface shadow-default`;
|
|
66
|
+
case "assistant":
|
|
67
|
+
return `${e} ${t.assistant} btn-assistant shadow-default`;
|
|
68
|
+
case "destructive":
|
|
69
|
+
return `${e} ${t.destructive} btn-destructive`;
|
|
70
|
+
case "success":
|
|
71
|
+
return `${e} ${t.success} btn-glass-success`;
|
|
72
|
+
case "warning":
|
|
73
|
+
return `${e} ${t.warning} btn-glass-warning`;
|
|
74
|
+
case "error":
|
|
75
|
+
return `${e} ${t.error} btn-glass-error`;
|
|
76
|
+
case "ghost":
|
|
77
|
+
return `${e} ${t.neutral} btn-ghost`;
|
|
78
|
+
case "quiet":
|
|
79
|
+
return `${e} ${t.accent} btn-quiet`;
|
|
80
|
+
case "no-style":
|
|
81
|
+
return `${e} ${t.neutral} btn-no-style border-none`;
|
|
82
|
+
default:
|
|
83
|
+
return `${e} ${t.neutral} btn-filled shadow-default`;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
export {
|
|
87
|
+
n as getDefaultRadiusClass,
|
|
88
|
+
l as getIconGapClass,
|
|
89
|
+
i as getRadiusClass,
|
|
90
|
+
s as getSizeClasses,
|
|
91
|
+
u as getVariantClasses
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=buttonStyles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buttonStyles.js","sources":["../../src/components/buttonStyles.ts"],"sourcesContent":["import type { ButtonRadius, ButtonSize, ButtonVariant } from './Button';\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.\nexport const 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// `rounded-*` reads the malachite radius tokens via `@theme`, so these follow the theme.\n// An explicit `radius` prop still wins, via getRadiusClass.\nexport const 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.\nexport const 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\nexport const 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\nexport const 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"],"names":["getSizeClasses","size","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getRadiusClass","radius","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses"],"mappings":"AAMO,MAAMA,IAAiB,CAACC,MAAqB;AAChD,UAAQA,GAAA;AAAA;AAAA,IAEJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAIaC,IAAwB,CAACD,GAAkBE,MAA8B;AAClF,UAAQF,GAAA;AAAA;AAAA,IAEJ,KAAK;AACD,aAAOE,IAAW,eAAe;AAAA,IACrC,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAGaC,IAAkB,CAACH,GAAkBI,MAAsC;AACpF,QAAMC,IAAQD,MAAa;AAC3B,UAAQJ,GAAA;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AACD,aAAOK,IAAQ,aAAa;AAAA,IAChC;AACI,aAAOA,IAAQ,SAAS;AAAA,EAAA;AAEpC,GAEaC,IAAiB,CAACC,MAAiC;AAC5D,UAAQA,GAAA;AAAA,IACJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAOMC,IAAgB;AAAA,EAClB,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,WAAW;AAAA,EACX,QAAQ;AACZ,GAEaC,IAAoB,CAACC,GAAwBC,MAAgC;AACtF,QAAMC,IAAc,4KAA4KD,CAAW;AAE3M,UAAQD,GAAA;AAAA,IACJ,KAAK;AACD,aAAO,GAAGE,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,SAAS;AAAA,IAEpD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,WAAW;AAAA,IAEtD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,KAAK;AAAA,IAEhD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,MAAM;AAAA,IAEjD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD;AACI,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,EAAA;AAE1D;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floatingMenuGeometry.js","sources":["../../src/components/floatingMenuGeometry.ts"],"sourcesContent":["// Figma: `Dropdown Component - Item List` fills its trigger, 10px below — 444 (68:2789),\n// 200 (2343:2201), 414 (1437:2986). `Dropdown Component - Options List` (213:4320) is a\n// fixed 280px, 14px below. Figma declares no trigger width and no floor; 160 and 400 are ours.\n\nexport const MENU_VIEWPORT_PADDING_PX = 8;\n\nexport const SIDE_PANEL_MIN_HEIGHT_PX = 160;\n\nexport const MENU_MIN_WIDTH_PX = 160;\n\n// Figma draws the Options menu at a flat 280. Taken as a ceiling rather than a width: a kebab\n// of four short verbs then carries 130px of air, and every kebab in a consuming app is the same\n// width whatever it holds. 180 is the floor those menus sized themselves to before this preset.\nexport const MENU_OPTIONS_MIN_WIDTH_PX = 180;\n\nexport type MenuPreset = 'standard' | 'options';\n\nexport interface MenuSpec {\n /** `'trigger'` fills the control that opened it; `'content'` sizes to the widest item. */\n width: 'trigger' | 'content';\n /** Where this preset stops shrinking. */\n minWidth: number;\n /** Where a content-sized one stops growing. A caller's `minWidth` still raises past it. */\n maxWidth?: number;\n align: 'left' | 'right';\n gap: number;\n radius: number;\n padding: number;\n shadow: string;\n background: string;\n border: string;\n}\n\nexport const MENU_SPECS: Record<MenuPreset, MenuSpec> = {\n standard: {\n width: 'trigger',\n minWidth: MENU_MIN_WIDTH_PX,\n align: 'left',\n gap: 10,\n radius: 12,\n padding: 4,\n shadow: '0px 2px 5px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n options: {\n width: 'content',\n minWidth: MENU_OPTIONS_MIN_WIDTH_PX,\n maxWidth: 280,\n align: 'right',\n gap: 14,\n radius: 12,\n padding: 8,\n shadow: '0px 4px 10px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n};\n\nexport const MENU_MAX_HEIGHT_PX = 400;\n\nexport interface MenuGeometry {\n top: number;\n left: number;\n width: number;\n maxHeight: number;\n openUp: boolean;\n}\n\nexport interface MenuGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom' | 'width'>;\n preset: MenuPreset;\n minWidth?: number;\n /** Which trigger edge the menu prefers to share; defaults to the preset's. */\n align?: 'left' | 'right';\n /** The widest item, for a content-sized preset. Absent until the menu has been laid out. */\n contentWidth?: number;\n menuHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport const computeMenuGeometry = ({\n triggerRect,\n preset,\n minWidth,\n align: alignOverride,\n contentWidth,\n menuHeight,\n viewportWidth,\n viewportHeight,\n}: MenuGeometryInput): MenuGeometry => {\n const spec = MENU_SPECS[preset];\n const available = viewportWidth - MENU_VIEWPORT_PADDING_PX * 2;\n\n const floor = Math.max(spec.minWidth, minWidth ?? 0);\n const ceiling = Math.min(available, Math.max(spec.maxWidth ?? available, floor));\n const natural = spec.width === 'trigger' ? triggerRect.width : (contentWidth ?? ceiling);\n const width = Math.max(0, Math.min(Math.max(natural, floor), ceiling));\n\n const align = alignOverride ?? spec.align;\n const preferredLeft = align === 'right' ? triggerRect.right - width : triggerRect.left;\n const oppositeLeft = align === 'right' ? triggerRect.left : triggerRect.right - width;\n const minLeft = MENU_VIEWPORT_PADDING_PX;\n const maxLeft = viewportWidth - width - MENU_VIEWPORT_PADDING_PX;\n const fits = (candidate: number) => candidate >= minLeft && candidate <= maxLeft;\n\n let left: number;\n if (fits(preferredLeft)) {\n left = preferredLeft;\n } else if (fits(oppositeLeft)) {\n left = oppositeLeft;\n } else {\n left = Math.max(minLeft, Math.min(preferredLeft, maxLeft));\n }\n\n // Which side, then how much of it. \"More room above\" is not \"enough room above\", so the\n // height is clamped to whichever side won. Clamping `top` instead keeps the menu on screen\n // by painting it over its own trigger, which also swallows the click that closes it.\n const roomBelow = viewportHeight - triggerRect.bottom - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const roomAbove = triggerRect.top - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const openUp = menuHeight > 0 && menuHeight > roomBelow && roomAbove > roomBelow;\n\n const maxHeight = Math.max(0, Math.min(MENU_MAX_HEIGHT_PX, openUp ? roomAbove : roomBelow));\n const height = menuHeight > 0 ? Math.min(menuHeight, maxHeight) : 0;\n\n const top = Math.max(\n MENU_VIEWPORT_PADDING_PX,\n openUp ? triggerRect.top - spec.gap - height : triggerRect.bottom + spec.gap,\n );\n\n return { top, left, width, maxHeight, openUp };\n};\n\n// The height decides whether the menu opens upwards, and it is only meaningful at the width the\n// menu will actually have — so on the pass that first sizes the panel the width is written\n// before the height is read. The guard is what keeps that out of the steady state: every later\n// call finds the width already set and writes nothing, so a `ResizeObserver` callback does not\n// resize what it is observing (Chrome's \"undelivered notifications\" error) and a per-frame\n// reposition does not force a reflow. After the first pass this is a pure read.\n// A content-sized preset needs the width the panel chose for itself, which is only readable\n// while no width has been written to it — i.e. on the pass that opens the menu. The caller keeps\n// it from there, because after the first write `offsetWidth` reports what we wrote, not what the\n// items want.\nexport const measureContentWidth = (panel: HTMLElement | null): number | undefined => {\n if (!panel || panel.style.width !== '') {\n return undefined;\n }\n\n // The rows, not the panel. Each label carries `min-width: 0` and `overflow: hidden` so it\n // can ellipsise, which also means it contributes nothing to the panel's intrinsic width —\n // measuring the panel itself returns a box the size of its own padding. `scrollWidth` on a\n // clipped row is the width the row wanted. It is rounded to a whole pixel, so a label\n // 144.2px wide reports 144 and ellipsises at that width; the extra pixel covers the fraction.\n const rows = panel.querySelectorAll<HTMLElement>('button, [role=\"menuitem\"]');\n let widest = 0;\n rows.forEach((row) => {\n widest = Math.max(widest, row.scrollWidth);\n });\n\n if (widest === 0) {\n return undefined;\n }\n\n // The panel is border-box, so the width we hand back has to carry its own padding and rule.\n // Each read is defaulted: a computed style can come back empty — jsdom never expands the\n // `border` shorthand — and one NaN here would make the whole width NaN, which CSS drops,\n // leaving the panel unsized with no sign of why.\n const styles = getComputedStyle(panel);\n const edge = (value: string) => Number.parseFloat(value) || 0;\n const chrome =\n edge(styles.paddingLeft) +\n edge(styles.paddingRight) +\n edge(styles.borderLeftWidth) +\n edge(styles.borderRightWidth);\n\n return Math.ceil(widest + 1 + chrome);\n};\n\nexport const measureMenuGeometry = (\n panel: HTMLElement | null,\n input: Omit<MenuGeometryInput, 'menuHeight'>,\n): MenuGeometry => {\n if (!panel) {\n return computeMenuGeometry({ ...input, menuHeight: 0 });\n }\n\n const cssWidth = `${computeMenuGeometry({ ...input, menuHeight: 0 }).width}px`;\n if (panel.style.width !== cssWidth) {\n panel.style.width = cssWidth;\n }\n\n return computeMenuGeometry({ ...input, menuHeight: panel.offsetHeight });\n};\n\nexport const isTriggerOffscreen = (\n rect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>,\n viewportWidth: number,\n viewportHeight: number,\n): boolean =>\n rect.bottom < 0 || rect.top > viewportHeight || rect.right < 0 || rect.left > viewportWidth;\n\nexport const rectSignature = (rect: Pick<DOMRect, 'top' | 'left' | 'width' | 'height'>): string =>\n `${rect.top},${rect.left},${rect.width},${rect.height}`;\n\nexport type FloatingPlacement =\n | 'bottom-start'\n | 'bottom-end'\n | 'top-start'\n | 'top-center'\n | 'right-start'\n | 'right-end'\n | 'left-start';\n\nconst alignedLeft = (\n align: 'start' | 'end' | 'center',\n triggerRect: Pick<DOMRect, 'left' | 'right'>,\n panelWidth: number,\n): number => {\n if (align === 'end') {\n return triggerRect.right - panelWidth;\n }\n if (align === 'center') {\n return (triggerRect.left + triggerRect.right - panelWidth) / 2;\n }\n return triggerRect.left;\n};\n\nexport const fullPanelHeight = (panel: HTMLElement): number =>\n panel.scrollHeight + panel.offsetHeight - panel.clientHeight;\n\nexport const FLOATING_OFFSET_PX = { small: 8, medium: 12, large: 14 } as const;\n\nexport type FloatingOffset = keyof typeof FLOATING_OFFSET_PX;\n\nexport interface FloatingGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>;\n placement: FloatingPlacement;\n panelWidth: number;\n panelHeight: number;\n gap: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport interface FloatingGeometry {\n top: number;\n left: number;\n maxHeight: number;\n side: 'top' | 'bottom' | 'left' | 'right';\n}\n\nconst clampToViewport = (value: number, size: number, viewport: number): number =>\n Math.max(MENU_VIEWPORT_PADDING_PX, Math.min(value, viewport - size - MENU_VIEWPORT_PADDING_PX));\n\nexport const computeFloatingGeometry = ({\n triggerRect,\n placement,\n panelWidth,\n panelHeight,\n gap,\n viewportWidth,\n viewportHeight,\n}: FloatingGeometryInput): FloatingGeometry => {\n const [preferred, align] = placement.split('-') as [\n FloatingGeometry['side'],\n 'start' | 'end' | 'center',\n ];\n const pad = MENU_VIEWPORT_PADDING_PX;\n\n if (preferred === 'top' || preferred === 'bottom') {\n const roomBelow = viewportHeight - triggerRect.bottom - gap - pad;\n const roomAbove = triggerRect.top - gap - pad;\n const wanted = preferred === 'bottom' ? roomBelow : roomAbove;\n const opposite = preferred === 'bottom' ? roomAbove : roomBelow;\n const side: FloatingGeometry['side'] =\n panelHeight > wanted && opposite > wanted\n ? preferred === 'bottom'\n ? 'top'\n : 'bottom'\n : preferred;\n const belowTop = Math.max(pad, triggerRect.bottom + gap);\n const aboveBottom = Math.min(viewportHeight - pad, triggerRect.top - gap);\n const maxHeight = Math.max(\n 0,\n side === 'bottom' ? viewportHeight - belowTop - pad : aboveBottom - pad,\n );\n const height = Math.min(panelHeight, maxHeight);\n const top = side === 'bottom' ? belowTop : Math.max(pad, aboveBottom - height);\n const left = clampToViewport(\n alignedLeft(align, triggerRect, panelWidth),\n panelWidth,\n viewportWidth,\n );\n return { top, left, maxHeight, side };\n }\n\n const roomRight = viewportWidth - triggerRect.right - gap - pad;\n const roomLeft = triggerRect.left - gap - pad;\n const wanted = preferred === 'right' ? roomRight : roomLeft;\n const opposite = preferred === 'right' ? roomLeft : roomRight;\n const side: FloatingGeometry['side'] =\n panelWidth > wanted && opposite > wanted\n ? preferred === 'right'\n ? 'left'\n : 'right'\n : preferred;\n const left = side === 'right' ? triggerRect.right + gap : triggerRect.left - gap - panelWidth;\n const levelTop = Math.max(pad, triggerRect.top);\n const roomBelow = viewportHeight - levelTop - pad;\n if (align === 'start' && roomBelow >= Math.min(panelHeight, SIDE_PANEL_MIN_HEIGHT_PX)) {\n return {\n top: levelTop,\n left: clampToViewport(left, panelWidth, viewportWidth),\n maxHeight: roomBelow,\n side,\n };\n }\n const maxHeight = Math.max(0, viewportHeight - pad * 2);\n const height = Math.min(panelHeight, maxHeight);\n const top = clampToViewport(\n align === 'end' ? triggerRect.bottom - height : triggerRect.top,\n height,\n viewportHeight,\n );\n return { top, left: clampToViewport(left, panelWidth, viewportWidth), maxHeight, side };\n};\n"],"names":["MENU_VIEWPORT_PADDING_PX","SIDE_PANEL_MIN_HEIGHT_PX","MENU_MIN_WIDTH_PX","MENU_OPTIONS_MIN_WIDTH_PX","MENU_SPECS","MENU_MAX_HEIGHT_PX","computeMenuGeometry","triggerRect","preset","minWidth","alignOverride","contentWidth","menuHeight","viewportWidth","viewportHeight","spec","available","floor","ceiling","natural","width","align","preferredLeft","oppositeLeft","minLeft","maxLeft","fits","candidate","left","roomBelow","roomAbove","openUp","maxHeight","height","measureContentWidth","panel","rows","widest","row","styles","edge","value","chrome","measureMenuGeometry","input","cssWidth","isTriggerOffscreen","rect","rectSignature","alignedLeft","panelWidth","fullPanelHeight","FLOATING_OFFSET_PX","clampToViewport","size","viewport","computeFloatingGeometry","placement","panelHeight","gap","preferred","pad","wanted","opposite","side","belowTop","aboveBottom","top","roomRight","roomLeft","levelTop"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"floatingMenuGeometry.js","sources":["../../src/components/floatingMenuGeometry.ts"],"sourcesContent":["// Figma: `Dropdown Component - Item List` fills its trigger, 10px below — 444 (68:2789),\n// 200 (2343:2201), 414 (1437:2986). `Dropdown Component - Options List` (213:4320) is a\n// fixed 280px, 14px below. Figma declares no trigger width and no floor; 160 and 400 are ours.\n\nexport const MENU_VIEWPORT_PADDING_PX = 8;\n\nexport const SIDE_PANEL_MIN_HEIGHT_PX = 160;\n\nexport const MENU_MIN_WIDTH_PX = 160;\n\n// Figma draws the Options menu at a flat 280. Taken as a ceiling rather than a width: a kebab\n// of four short verbs then carries 130px of air, and every kebab in a consuming app is the same\n// width whatever it holds. 180 is the floor those menus sized themselves to before this preset.\nexport const MENU_OPTIONS_MIN_WIDTH_PX = 180;\n\nexport type MenuPreset = 'standard' | 'options';\n\nexport interface MenuSpec {\n /** `'trigger'` fills the control that opened it; `'content'` sizes to the widest item. */\n width: 'trigger' | 'content';\n /** Where this preset stops shrinking. */\n minWidth: number;\n /** Where a content-sized one stops growing. A caller's `minWidth` still raises past it. */\n maxWidth?: number;\n align: 'left' | 'right';\n gap: number;\n radius: number;\n padding: number;\n shadow: string;\n background: string;\n border: string;\n}\n\nexport const MENU_SPECS: Record<MenuPreset, MenuSpec> = {\n standard: {\n width: 'trigger',\n minWidth: MENU_MIN_WIDTH_PX,\n align: 'left',\n gap: 10,\n radius: 12,\n padding: 4,\n shadow: '0px 2px 5px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n options: {\n width: 'content',\n minWidth: MENU_OPTIONS_MIN_WIDTH_PX,\n maxWidth: 280,\n align: 'right',\n gap: 14,\n radius: 12,\n padding: 8,\n shadow: '0px 4px 10px 0px rgba(0, 0, 0, 0.1)',\n background: 'var(--malachite-card-background, rgb(255, 255, 255))',\n border: '1px solid var(--malachite-card-border, rgb(232, 232, 232))',\n },\n};\n\nexport const MENU_MAX_HEIGHT_PX = 400;\n\nexport interface MenuGeometry {\n top: number;\n left: number;\n width: number;\n maxHeight: number;\n openUp: boolean;\n}\n\nexport interface MenuGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom' | 'width'>;\n preset: MenuPreset;\n minWidth?: number;\n /** Which trigger edge the menu prefers to share; defaults to the preset's. */\n align?: 'left' | 'right';\n /** The widest item, for a content-sized preset. Absent until the menu has been laid out. */\n contentWidth?: number;\n menuHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport const computeMenuGeometry = ({\n triggerRect,\n preset,\n minWidth,\n align: alignOverride,\n contentWidth,\n menuHeight,\n viewportWidth,\n viewportHeight,\n}: MenuGeometryInput): MenuGeometry => {\n const spec = MENU_SPECS[preset];\n const available = viewportWidth - MENU_VIEWPORT_PADDING_PX * 2;\n\n const floor = Math.max(spec.minWidth, minWidth ?? 0);\n const ceiling = Math.min(available, Math.max(spec.maxWidth ?? available, floor));\n const natural = spec.width === 'trigger' ? triggerRect.width : (contentWidth ?? ceiling);\n const width = Math.max(0, Math.min(Math.max(natural, floor), ceiling));\n\n const align = alignOverride ?? spec.align;\n const preferredLeft = align === 'right' ? triggerRect.right - width : triggerRect.left;\n const oppositeLeft = align === 'right' ? triggerRect.left : triggerRect.right - width;\n const minLeft = MENU_VIEWPORT_PADDING_PX;\n const maxLeft = viewportWidth - width - MENU_VIEWPORT_PADDING_PX;\n const fits = (candidate: number) => candidate >= minLeft && candidate <= maxLeft;\n\n let left: number;\n if (fits(preferredLeft)) {\n left = preferredLeft;\n } else if (fits(oppositeLeft)) {\n left = oppositeLeft;\n } else {\n left = Math.max(minLeft, Math.min(preferredLeft, maxLeft));\n }\n\n // Which side, then how much of it. \"More room above\" is not \"enough room above\", so the\n // height is clamped to whichever side won. Clamping `top` instead keeps the menu on screen\n // by painting it over its own trigger, which also swallows the click that closes it.\n const roomBelow = viewportHeight - triggerRect.bottom - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const roomAbove = triggerRect.top - spec.gap - MENU_VIEWPORT_PADDING_PX;\n const openUp = menuHeight > 0 && menuHeight > roomBelow && roomAbove > roomBelow;\n\n const maxHeight = Math.max(0, Math.min(MENU_MAX_HEIGHT_PX, openUp ? roomAbove : roomBelow));\n const height = menuHeight > 0 ? Math.min(menuHeight, maxHeight) : 0;\n\n const top = Math.max(\n MENU_VIEWPORT_PADDING_PX,\n openUp ? triggerRect.top - spec.gap - height : triggerRect.bottom + spec.gap,\n );\n\n return { top, left, width, maxHeight, openUp };\n};\n\n// The height decides whether the menu opens upwards, and it is only meaningful at the width the\n// menu will actually have — so on the pass that first sizes the panel the width is written\n// before the height is read. The guard is what keeps that out of the steady state: every later\n// call finds the width already set and writes nothing, so a `ResizeObserver` callback does not\n// resize what it is observing (Chrome's \"undelivered notifications\" error) and a per-frame\n// reposition does not force a reflow. After the first pass this is a pure read.\n// A content-sized preset needs the width the panel chose for itself, which is only readable\n// while no width has been written to it — i.e. on the pass that opens the menu. The caller keeps\n// it from there, because after the first write `offsetWidth` reports what we wrote, not what the\n// items want.\nexport const measureContentWidth = (panel: HTMLElement | null): number | undefined => {\n if (!panel || panel.style.width !== '') {\n return undefined;\n }\n\n // The rows, not the panel. Each label carries `min-width: 0` and `overflow: hidden` so it\n // can ellipsise, which also means it contributes nothing to the panel's intrinsic width —\n // measuring the panel itself returns a box the size of its own padding. `scrollWidth` on a\n // clipped row is the width the row wanted. It is rounded to a whole pixel, so a label\n // 144.2px wide reports 144 and ellipsises at that width; the extra pixel covers the fraction.\n const rows = panel.querySelectorAll<HTMLElement>('button, [role=\"menuitem\"]');\n let widest = 0;\n rows.forEach((row) => {\n widest = Math.max(widest, row.scrollWidth);\n });\n\n if (widest === 0) {\n return undefined;\n }\n\n // The panel is border-box, so the width we hand back has to carry its own padding and rule.\n // Each read is defaulted: a computed style can come back empty — jsdom never expands the\n // `border` shorthand — and one NaN here would make the whole width NaN, which CSS drops,\n // leaving the panel unsized with no sign of why.\n const styles = getComputedStyle(panel);\n const edge = (value: string) => Number.parseFloat(value) || 0;\n const chrome =\n edge(styles.paddingLeft) +\n edge(styles.paddingRight) +\n edge(styles.borderLeftWidth) +\n edge(styles.borderRightWidth);\n\n return Math.ceil(widest + 1 + chrome);\n};\n\nexport const measureMenuGeometry = (\n panel: HTMLElement | null,\n input: Omit<MenuGeometryInput, 'menuHeight'>,\n): MenuGeometry => {\n if (!panel) {\n return computeMenuGeometry({ ...input, menuHeight: 0 });\n }\n\n const cssWidth = `${computeMenuGeometry({ ...input, menuHeight: 0 }).width}px`;\n if (panel.style.width !== cssWidth) {\n panel.style.width = cssWidth;\n }\n\n return computeMenuGeometry({ ...input, menuHeight: panel.offsetHeight });\n};\n\nexport const isTriggerOffscreen = (\n rect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>,\n viewportWidth: number,\n viewportHeight: number,\n): boolean =>\n rect.bottom < 0 || rect.top > viewportHeight || rect.right < 0 || rect.left > viewportWidth;\n\nexport const rectSignature = (rect: Pick<DOMRect, 'top' | 'left' | 'width' | 'height'>): string =>\n `${rect.top},${rect.left},${rect.width},${rect.height}`;\n\nexport type FloatingPlacement =\n | 'bottom-start'\n | 'bottom-end'\n | 'top-start'\n | 'top-end'\n | 'top-center'\n | 'right-start'\n | 'right-end'\n | 'left-start';\n\nconst alignedLeft = (\n align: 'start' | 'end' | 'center',\n triggerRect: Pick<DOMRect, 'left' | 'right'>,\n panelWidth: number,\n): number => {\n if (align === 'end') {\n return triggerRect.right - panelWidth;\n }\n if (align === 'center') {\n return (triggerRect.left + triggerRect.right - panelWidth) / 2;\n }\n return triggerRect.left;\n};\n\nexport const fullPanelHeight = (panel: HTMLElement): number =>\n panel.scrollHeight + panel.offsetHeight - panel.clientHeight;\n\nexport const FLOATING_OFFSET_PX = { small: 8, medium: 12, large: 14 } as const;\n\nexport type FloatingOffset = keyof typeof FLOATING_OFFSET_PX;\n\nexport interface FloatingGeometryInput {\n triggerRect: Pick<DOMRect, 'top' | 'left' | 'right' | 'bottom'>;\n placement: FloatingPlacement;\n panelWidth: number;\n panelHeight: number;\n gap: number;\n viewportWidth: number;\n viewportHeight: number;\n}\n\nexport interface FloatingGeometry {\n top: number;\n left: number;\n maxHeight: number;\n side: 'top' | 'bottom' | 'left' | 'right';\n}\n\nconst clampToViewport = (value: number, size: number, viewport: number): number =>\n Math.max(MENU_VIEWPORT_PADDING_PX, Math.min(value, viewport - size - MENU_VIEWPORT_PADDING_PX));\n\nexport const computeFloatingGeometry = ({\n triggerRect,\n placement,\n panelWidth,\n panelHeight,\n gap,\n viewportWidth,\n viewportHeight,\n}: FloatingGeometryInput): FloatingGeometry => {\n const [preferred, align] = placement.split('-') as [\n FloatingGeometry['side'],\n 'start' | 'end' | 'center',\n ];\n const pad = MENU_VIEWPORT_PADDING_PX;\n\n if (preferred === 'top' || preferred === 'bottom') {\n const roomBelow = viewportHeight - triggerRect.bottom - gap - pad;\n const roomAbove = triggerRect.top - gap - pad;\n const wanted = preferred === 'bottom' ? roomBelow : roomAbove;\n const opposite = preferred === 'bottom' ? roomAbove : roomBelow;\n const side: FloatingGeometry['side'] =\n panelHeight > wanted && opposite > wanted\n ? preferred === 'bottom'\n ? 'top'\n : 'bottom'\n : preferred;\n const belowTop = Math.max(pad, triggerRect.bottom + gap);\n const aboveBottom = Math.min(viewportHeight - pad, triggerRect.top - gap);\n const maxHeight = Math.max(\n 0,\n side === 'bottom' ? viewportHeight - belowTop - pad : aboveBottom - pad,\n );\n const height = Math.min(panelHeight, maxHeight);\n const top = side === 'bottom' ? belowTop : Math.max(pad, aboveBottom - height);\n const left = clampToViewport(\n alignedLeft(align, triggerRect, panelWidth),\n panelWidth,\n viewportWidth,\n );\n return { top, left, maxHeight, side };\n }\n\n const roomRight = viewportWidth - triggerRect.right - gap - pad;\n const roomLeft = triggerRect.left - gap - pad;\n const wanted = preferred === 'right' ? roomRight : roomLeft;\n const opposite = preferred === 'right' ? roomLeft : roomRight;\n const side: FloatingGeometry['side'] =\n panelWidth > wanted && opposite > wanted\n ? preferred === 'right'\n ? 'left'\n : 'right'\n : preferred;\n const left = side === 'right' ? triggerRect.right + gap : triggerRect.left - gap - panelWidth;\n const levelTop = Math.max(pad, triggerRect.top);\n const roomBelow = viewportHeight - levelTop - pad;\n if (align === 'start' && roomBelow >= Math.min(panelHeight, SIDE_PANEL_MIN_HEIGHT_PX)) {\n return {\n top: levelTop,\n left: clampToViewport(left, panelWidth, viewportWidth),\n maxHeight: roomBelow,\n side,\n };\n }\n const maxHeight = Math.max(0, viewportHeight - pad * 2);\n const height = Math.min(panelHeight, maxHeight);\n const top = clampToViewport(\n align === 'end' ? triggerRect.bottom - height : triggerRect.top,\n height,\n viewportHeight,\n );\n return { top, left: clampToViewport(left, panelWidth, viewportWidth), maxHeight, side };\n};\n"],"names":["MENU_VIEWPORT_PADDING_PX","SIDE_PANEL_MIN_HEIGHT_PX","MENU_MIN_WIDTH_PX","MENU_OPTIONS_MIN_WIDTH_PX","MENU_SPECS","MENU_MAX_HEIGHT_PX","computeMenuGeometry","triggerRect","preset","minWidth","alignOverride","contentWidth","menuHeight","viewportWidth","viewportHeight","spec","available","floor","ceiling","natural","width","align","preferredLeft","oppositeLeft","minLeft","maxLeft","fits","candidate","left","roomBelow","roomAbove","openUp","maxHeight","height","measureContentWidth","panel","rows","widest","row","styles","edge","value","chrome","measureMenuGeometry","input","cssWidth","isTriggerOffscreen","rect","rectSignature","alignedLeft","panelWidth","fullPanelHeight","FLOATING_OFFSET_PX","clampToViewport","size","viewport","computeFloatingGeometry","placement","panelHeight","gap","preferred","pad","wanted","opposite","side","belowTop","aboveBottom","top","roomRight","roomLeft","levelTop"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -208,6 +208,8 @@ declare interface AlertProps {
|
|
|
208
208
|
renderLink?: RenderLink;
|
|
209
209
|
/** `banner`: a 48px page-wide band. Applies only without `children`. */
|
|
210
210
|
layout?: AlertLayout;
|
|
211
|
+
/** `center` gathers a banner's icon and text in the middle of the band, for a wide page. */
|
|
212
|
+
align?: 'start' | 'center';
|
|
211
213
|
/** A bold sentence the message runs on from. Plain text, not markdown. */
|
|
212
214
|
lead?: string;
|
|
213
215
|
}
|
|
@@ -290,6 +292,30 @@ export declare const Avatar: default_2.FC<AvatarProps>;
|
|
|
290
292
|
/** `initial` suits a named entity (a person, a model); `placeholder` says "no logo available". */
|
|
291
293
|
export declare type AvatarFallback = 'initial' | 'placeholder';
|
|
292
294
|
|
|
295
|
+
/** Overlapping `Avatar`s, first on top; one `img` to assistive tech, named by its items. */
|
|
296
|
+
export declare const AvatarGroup: default_2.FC<AvatarGroupProps>;
|
|
297
|
+
|
|
298
|
+
export declare interface AvatarGroupItem {
|
|
299
|
+
src?: string;
|
|
300
|
+
name: string;
|
|
301
|
+
fallback?: AvatarFallback;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export declare interface AvatarGroupProps {
|
|
305
|
+
items: AvatarGroupItem[];
|
|
306
|
+
size?: number;
|
|
307
|
+
shape?: AvatarShape;
|
|
308
|
+
/** Tiles shown before a `+N` tile takes the rest. */
|
|
309
|
+
max?: number;
|
|
310
|
+
/** Fraction of `size` (0 to 0.5) each tile sits under the one before it. */
|
|
311
|
+
overlap?: number;
|
|
312
|
+
/** Names the group; otherwise it is named by the item names joined with ", ". */
|
|
313
|
+
'aria-label'?: string;
|
|
314
|
+
'aria-labelledby'?: string;
|
|
315
|
+
className?: string;
|
|
316
|
+
style?: default_2.CSSProperties;
|
|
317
|
+
}
|
|
318
|
+
|
|
293
319
|
export declare interface AvatarProps {
|
|
294
320
|
/** Image URL (logo/photo). Falls back to `fallback` when missing or it fails to load. */
|
|
295
321
|
src?: string;
|
|
@@ -1675,6 +1701,8 @@ declare interface EllipsisTextProps {
|
|
|
1675
1701
|
*/
|
|
1676
1702
|
tooltipWhenTruncated?: boolean;
|
|
1677
1703
|
tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';
|
|
1704
|
+
/** A span around the text, for a place a div may not go, such as inside a clickable `Surface.Row`. */
|
|
1705
|
+
inline?: boolean;
|
|
1678
1706
|
className?: string;
|
|
1679
1707
|
style?: default_2.CSSProperties;
|
|
1680
1708
|
}
|
|
@@ -1884,6 +1912,8 @@ declare interface FlexProps {
|
|
|
1884
1912
|
wrap?: boolean;
|
|
1885
1913
|
width?: string;
|
|
1886
1914
|
id?: string;
|
|
1915
|
+
/** `span` for content inside a button, such as a clickable `Surface.Row`. */
|
|
1916
|
+
as?: 'div' | 'span';
|
|
1887
1917
|
children?: default_2.ReactNode;
|
|
1888
1918
|
className?: string;
|
|
1889
1919
|
}
|
|
@@ -1896,7 +1926,7 @@ declare const FLOATING_OFFSET_PX: {
|
|
|
1896
1926
|
|
|
1897
1927
|
declare type FloatingOffset = keyof typeof FLOATING_OFFSET_PX;
|
|
1898
1928
|
|
|
1899
|
-
declare type FloatingPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-center' | 'right-start' | 'right-end' | 'left-start';
|
|
1929
|
+
declare type FloatingPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' | 'top-center' | 'right-start' | 'right-end' | 'left-start';
|
|
1900
1930
|
|
|
1901
1931
|
/** Which theme typeface to render in. Defaults to the sans body face. */
|
|
1902
1932
|
declare type FontFamily = 'sans' | 'heading' | 'body' | 'mono';
|
|
@@ -4622,6 +4652,7 @@ export declare const Sidebar: default_2.FC<SidebarProps> & {
|
|
|
4622
4652
|
Usage: default_2.FC<UsageCardProps>;
|
|
4623
4653
|
Panel: default_2.FC<{
|
|
4624
4654
|
children: default_2.ReactNode;
|
|
4655
|
+
width?: number;
|
|
4625
4656
|
}>;
|
|
4626
4657
|
};
|
|
4627
4658
|
|
|
@@ -4800,6 +4831,36 @@ declare interface SpinnerProps {
|
|
|
4800
4831
|
style?: default_2.CSSProperties;
|
|
4801
4832
|
}
|
|
4802
4833
|
|
|
4834
|
+
/** A primary action and a menu trigger in one pill; each segment is its own button. */
|
|
4835
|
+
export declare const SplitButton: default_2.ForwardRefExoticComponent<SplitButtonProps & default_2.RefAttributes<HTMLButtonElement>>;
|
|
4836
|
+
|
|
4837
|
+
export declare interface SplitButtonProps {
|
|
4838
|
+
variant?: ButtonVariant;
|
|
4839
|
+
size?: ButtonSize;
|
|
4840
|
+
children?: default_2.ReactNode;
|
|
4841
|
+
leading?: default_2.ReactNode;
|
|
4842
|
+
trailing?: default_2.ReactNode;
|
|
4843
|
+
onClick?: default_2.MouseEventHandler<HTMLButtonElement>;
|
|
4844
|
+
/** The trigger segment is always `type="button"`. */
|
|
4845
|
+
type?: 'button' | 'submit' | 'reset';
|
|
4846
|
+
loading?: boolean;
|
|
4847
|
+
disabled?: boolean;
|
|
4848
|
+
flexGrow?: boolean;
|
|
4849
|
+
/** Accessible name of the chevron segment. */
|
|
4850
|
+
menuLabel: string;
|
|
4851
|
+
/** Drives `aria-expanded`; the chevron points up while open. */
|
|
4852
|
+
menuOpen?: boolean;
|
|
4853
|
+
onMenuClick?: default_2.MouseEventHandler<HTMLButtonElement>;
|
|
4854
|
+
menuIcon?: default_2.ReactNode;
|
|
4855
|
+
/** `false` for a trigger that opens something other than a menu. */
|
|
4856
|
+
menuHasPopup?: boolean;
|
|
4857
|
+
menuRef?: default_2.Ref<HTMLButtonElement>;
|
|
4858
|
+
/** Wraps the trigger segment, e.g. as a `Popover`'s `trigger`. */
|
|
4859
|
+
menuTrigger?: (segment: default_2.ReactElement) => default_2.ReactNode;
|
|
4860
|
+
className?: string;
|
|
4861
|
+
style?: default_2.CSSProperties;
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4803
4864
|
export declare const SplitScreenLayout: default_2.FC<SplitScreenLayoutProps>;
|
|
4804
4865
|
|
|
4805
4866
|
declare interface SplitScreenLayoutProps {
|
|
@@ -5067,6 +5128,8 @@ export declare interface SurfaceRowProps extends SurfaceRowChromeProps {
|
|
|
5067
5128
|
role?: default_2.AriaRole;
|
|
5068
5129
|
'aria-label'?: string;
|
|
5069
5130
|
'aria-labelledby'?: string;
|
|
5131
|
+
/** `-1` takes the row out of the Tab order, for a roving radio group. */
|
|
5132
|
+
tabIndex?: number;
|
|
5070
5133
|
}
|
|
5071
5134
|
|
|
5072
5135
|
export declare const Swatch: default_2.FC<SwatchProps>;
|