Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion design-system/packages/design-tokens/src/system.tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"mono": {
"$type": "fontFamily",
"$value": "'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 'Cascadia Mono', 'Cascadia Code', Consolas, 'Liberation Mono', 'Courier New', monospace"
"$value": "'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 'Cascadia Mono', 'Cascadia Code', Consolas, 'Liberation Mono', 'Courier New', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', monospace"
}
},
"size": {
Expand Down Expand Up @@ -859,6 +859,10 @@
"menu": {
"$type": "dimension",
"inlineSize": { "$value": "220px" },
"minInlineSize": {
"$description": "Lower bound for content-sized menus; overlay.menu.inlineSize stays their upper bound.",
"$value": "160px"
},
"maxBlockSize": { "$value": "480px" },
"surfacePadding": { "$value": "{space.2}" },
"surfaceRadius": { "$value": "{radius.xl}" },
Expand Down
5 changes: 5 additions & 0 deletions design-system/packages/design-tokens/tests/contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ test("Menu tokens preserve the compact grouped surface contract", async () => {
const systemDocument = await readSource("system.tokens.json");

assert.equal(tokens["overlay.menu.inlineSize"], "220px");
assert.equal(tokens["overlay.menu.minInlineSize"], "160px");
assert.ok(
Number.parseFloat(tokens["overlay.menu.minInlineSize"]) < Number.parseFloat(tokens["overlay.menu.inlineSize"]),
"content-sized menus need a minimum strictly below the fixed width",
);
assert.equal(tokens["overlay.menu.maxBlockSize"], "480px");
assert.equal(tokens["overlay.menu.headingHeight"], "24px");
assert.equal(tokens["overlay.menu.itemHeight"], "30px");
Expand Down
6 changes: 6 additions & 0 deletions design-system/packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,12 @@ Product code owns positioning and viewport limits, and must not patch private
list/section-items/group-options gaps. A deliberate density variation belongs
on the owning surface via `--openbitfun-overlay-menu-row-gap`.

Menus use `overlay.menu.inlineSize` by default. `Menu` / `MenuPopover` accept
`inlineSize="content"` for short, product-owned surfaces such as a context menu:
the surface then hugs its widest row, stays at or above
`overlay.menu.minInlineSize`, and never exceeds the fixed token. Long menus that
share a column with the same triggering control keep the fixed width.

ActionItem hover and pressed surfaces use the semantic neutral hover fill;
pressed text remains semibold. Menu and navigation captions consume the final
caption color directly, avoiding a second opacity multiplier. The nested-menu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@
white-space: nowrap;
}

/* A text shortcut hint is secondary text, so it must not inherit the surface body size. */
.shortcut {
font-family: var(--openbitfun-type-meta-font-family);
font-size: var(--openbitfun-type-meta-font-size);
font-weight: var(--openbitfun-type-meta-font-weight);
line-height: var(--openbitfun-type-meta-line-height);
letter-spacing: var(--openbitfun-type-meta-letter-spacing);
white-space: nowrap;
}

.root[data-disabled="true"] .metadata {
color: inherit;
}
Expand Down
2 changes: 2 additions & 0 deletions design-system/packages/ui/src/components/Menu/Menu.meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const menuMeta = {
{ name: "MenuList.children", type: "ReactNode (row stack inside custom scroll or animation wrappers)" },
{ defaultValue: "false", name: "autoFocusFirstItem", type: "boolean" },
{ defaultValue: "auto", name: "scrollbarVisibility", type: "auto | always | hidden" },
{ defaultValue: "fixed", name: "inlineSize", type: "fixed | content" },
{ name: "MenuPopover.items", type: "readonly MenuEntry[]" },
{ name: "MenuPopover.open / onClose", type: "boolean / () => void" },
{ name: "MenuPopover.anchorRef / position", type: "RefObject<HTMLElement> / { x: number; y: number }" },
Expand All @@ -29,6 +30,7 @@ export const menuMeta = {
"color.selection.surface",
"color.focus.ring",
"overlay.menu.inlineSize",
"overlay.menu.minInlineSize",
"overlay.menu.maxBlockSize",
"overlay.menu.surfacePadding",
"overlay.menu.surfaceRadius",
Expand Down
7 changes: 7 additions & 0 deletions design-system/packages/ui/src/components/Menu/Menu.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
box-shadow: var(--openbitfun-shadow-menu);
}

/* Content-driven menus hug their widest row; the fixed token stays the upper bound. */
.root[data-openbitfun-inline-size="content"] {
inline-size: max-content;
min-inline-size: var(--openbitfun-overlay-menu-min-inline-size);
max-inline-size: min(var(--openbitfun-overlay-menu-inline-size), 100%);
}

.viewport {
flex: 1 1 auto;
min-inline-size: 0;
Expand Down
6 changes: 6 additions & 0 deletions design-system/packages/ui/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import styles from "./Menu.module.css";

export type MenuItemRole = "menuitem" | "menuitemcheckbox" | "menuitemradio";

/** `fixed` keeps the menu width token; `content` fits the rows between the menu minimum and that token. */
export type MenuInlineSize = "fixed" | "content";

export interface MenuProps
extends Omit<HTMLAttributes<HTMLDivElement>, "autoFocus" | "role"> {
autoFocusFirstItem?: boolean;
children: ReactNode;
inlineSize?: MenuInlineSize;
scrollbarVisibility?: ScrollbarVisibility;
}

Expand Down Expand Up @@ -105,6 +109,7 @@ export const Menu = forwardRef<HTMLDivElement, MenuProps>(function Menu({
autoFocusFirstItem = false,
children,
className,
inlineSize = "fixed",
onFocusCapture,
onKeyDown,
scrollbarVisibility = "auto",
Expand Down Expand Up @@ -210,6 +215,7 @@ export const Menu = forwardRef<HTMLDivElement, MenuProps>(function Menu({
{...props}
className={classNames(styles.root, className)}
data-openbitfun-component="menu"
data-openbitfun-inline-size={inlineSize}
onFocusCapture={handleFocusCapture}
onKeyDown={handleKeyDown}
ref={setRootRef}
Expand Down
6 changes: 3 additions & 3 deletions design-system/packages/ui/src/components/Menu/MenuPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ interface MenuLevelProps extends Omit<MenuProps, "children"> {
parts?: MenuPopoverParts;
}

function MenuLevel({ items, open, phase, treeId, onClose, onBack, anchorRef, position, placement, menuRef: externalRef, autoFocusFirstItem, className, style, parts, ...props }: MenuLevelProps) {
function MenuLevel({ items, open, phase, treeId, onClose, onBack, anchorRef, position, placement, menuRef: externalRef, autoFocusFirstItem, className, inlineSize, style, parts, ...props }: MenuLevelProps) {
const MenuSurface = parts?.root ?? Menu;
const Item = parts?.item ?? MenuItem;
const Separator = parts?.separator ?? MenuSeparator;
Expand Down Expand Up @@ -197,12 +197,12 @@ function MenuLevel({ items, open, phase, treeId, onClose, onBack, anchorRef, pos
return () => doc?.removeEventListener("keydown", keyboard, true);
});

const submenu = activeEntry ? <SubmenuBoundary className={styles.submenuBoundary}><MenuLevel key={activeEntry.id} id={submenuId} aria-label={activeEntry.label} menuRef={submenuRef} items={activeEntry.submenu!} open={open} phase={phase} treeId={treeId} onClose={onClose} parts={parts}
const submenu = activeEntry ? <SubmenuBoundary className={styles.submenuBoundary}><MenuLevel key={activeEntry.id} id={submenuId} aria-label={activeEntry.label} menuRef={submenuRef} items={activeEntry.submenu!} open={open} phase={phase} treeId={treeId} onClose={onClose} parts={parts} inlineSize={inlineSize}
onBack={() => { intent.closeNow(); submenuAnchor.current?.focus(); }} anchorRef={submenuAnchor} placement="right" autoFocusFirstItem={keyboardOpen.current}
onPointerEnter={intent.keepOpen} onPointerLeave={intent.requestClose} /></SubmenuBoundary> : null;

return <>
<MenuSurface {...props} ref={node => { (menuRef as { current: HTMLDivElement | null }).current = node; }} className={classNames(styles.popup, className)} autoFocusFirstItem={open && autoFocusFirstItem && Boolean(layout)} tabIndex={-1}
<MenuSurface {...props} ref={node => { (menuRef as { current: HTMLDivElement | null }).current = node; }} className={classNames(styles.popup, className)} inlineSize={inlineSize} autoFocusFirstItem={open && autoFocusFirstItem && Boolean(layout)} tabIndex={-1}
style={{ ...layout?.style, ...style, visibility: layout ? undefined : "hidden" }} data-openbitfun-native-webview-occlusion data-openbitfun-menu-tree={treeId} data-placement={layout?.placement ?? placement} data-state={phase}
aria-hidden={!open || undefined} {...(!open ? { inert: "" } : {})} onContextMenu={event => event.preventDefault()}>
{items.map(item => item.separator ? <Separator key={item.id} /> : <Item key={item.id} data-menu-id={item.id} leading={item.icon ? <Leading className={styles.icon} data-openbitfun-icon-slot="true">{item.icon}</Leading> : undefined} shortcut={item.shortcut ? <Shortcut>{item.shortcut}</Shortcut> : undefined} tone={item.tone} role={item.role} checked={item.checked}
Expand Down
21 changes: 21 additions & 0 deletions design-system/packages/ui/tests/menu.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,24 @@ test("Menu keeps equal item insets while its scrollbar stays on the surface edge
);
assert.doesNotMatch(styles, /scrollbar-gutter:\s*stable/);
});

test("content-sized menus hug their widest row inside the shared menu bounds", async () => {
const styles = await readFile(new URL("../src/components/Menu/Menu.module.css", import.meta.url), "utf8");
const popover = await readFile(new URL("../src/components/Menu/MenuPopover.tsx", import.meta.url), "utf8");
const contentRule = styles.match(/\.root\[data-openbitfun-inline-size="content"\]\s*\{[^}]*\}/)?.[0] ?? "";

assert.notEqual(contentRule, "");
assert.match(contentRule, /inline-size:\s*max-content/);
assert.match(contentRule, /min-inline-size:\s*var\(--openbitfun-overlay-menu-min-inline-size\)/);
assert.match(contentRule, /max-inline-size:\s*min\(var\(--openbitfun-overlay-menu-inline-size\), 100%\)/);
// The default surface keeps the fixed token instead of hugging content.
assert.match(styles, /\.root\s*\{[^}]*inline-size:\s*var\(--openbitfun-overlay-menu-inline-size\)/);

const contentMarkup = renderToStaticMarkup(createElement(Menu, { inlineSize: "content" }, createElement(MenuItem, null, "Paste")));
const defaultMarkup = renderToStaticMarkup(createElement(Menu, null, createElement(MenuItem, null, "Paste")));
assert.match(contentMarkup, /data-openbitfun-inline-size="content"/);
assert.match(defaultMarkup, /data-openbitfun-inline-size="fixed"/);
// Submenus are separate surfaces and must inherit the requested sizing mode.
assert.match(popover, /items=\{activeEntry\.submenu!\}[^>]*inlineSize=\{inlineSize\}/);
});

3 changes: 2 additions & 1 deletion src/apps/data-migrator/ui/generated/design-system.css
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
--openbitfun-focus-offset: 2px;
--openbitfun-focus-width: 2px;
--openbitfun-font-family-control: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
--openbitfun-font-family-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 'Cascadia Mono', 'Cascadia Code', Consolas, 'Liberation Mono', 'Courier New', monospace;
--openbitfun-font-family-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 'Cascadia Mono', 'Cascadia Code', Consolas, 'Liberation Mono', 'Courier New', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', monospace;
--openbitfun-font-family-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
--openbitfun-font-size-2xl: 18px;
--openbitfun-font-size-2xl-plus: 20px;
Expand Down Expand Up @@ -388,6 +388,7 @@
--openbitfun-overlay-menu-item-padding-inline: var(--openbitfun-space-2);
--openbitfun-overlay-menu-item-radius: var(--openbitfun-radius-base);
--openbitfun-overlay-menu-max-block-size: 480px;
--openbitfun-overlay-menu-min-inline-size: 160px;
--openbitfun-overlay-menu-row-gap: 2px;
--openbitfun-overlay-menu-scrollbar-gap: 2px;
--openbitfun-overlay-menu-section-gap: var(--openbitfun-space-2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
--openbitfun-radius: 8px;
--openbitfun-radius-lg: 12px;
--openbitfun-font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
--openbitfun-font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 'Cascadia Mono', 'Cascadia Code', Consolas, 'Liberation Mono', 'Courier New', monospace;
--openbitfun-font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, 'Cascadia Mono', 'Cascadia Code', Consolas, 'Liberation Mono', 'Courier New', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', monospace;
--openbitfun-bg: #0e0e10;
--openbitfun-bg-secondary: #1c1c1f;
--openbitfun-bg-tertiary: #0e0e10;
Expand Down
1 change: 0 additions & 1 deletion src/web-ui/src/app/components/NavPanel/NavPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,6 @@ $_section-header-height: 22px;
/* Positioning-only overrides; the design-system Menu owns the surface look. */
.openbitfun-nav-panel__footer-menu {
position: fixed;
min-width: 148px;
max-width: calc(100vw - 16px);
max-height: calc(100vh - 16px);
z-index: var(--openbitfun-layer-popover);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ const AppearanceQuickSwitchMenuItem: React.FC<AppearanceQuickSwitchMenuItemProps
<Menu
ref={submenuRef}
className="openbitfun-nav-panel__appearance-submenu"
inlineSize="content"
aria-label={t('nav.settingsMenu.theme')}
data-placement={submenuLayout?.placement}
data-testid="nav-settings-appearance-menu"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const AssistantSessionCreateMenu: React.FC<AssistantSessionCreateMenuProps> = ({
<Menu
ref={menuRef}
className="openbitfun-nav-panel__assistant-session-menu"
inlineSize="content"
aria-label={chooseAssistantLabel}
data-testid="nav-assistant-session-menu"
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ const PersistentFooterActions: React.FC = () => {
<Menu
ref={menuPopoverRef}
className={`openbitfun-nav-panel__footer-menu${menuClosing ? ' is-closing' : ''}`}
inlineSize="content"
aria-label={t('actions.more')}
data-testid="nav-settings-menu"
onKeyDown={(event) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,15 @@ const WorkspaceSessionFilterMenu: React.FC = () => {
const anchor = buttonRef.current?.getBoundingClientRect();
if (!anchor) return;
const measuredHeight = menuRef.current?.offsetHeight ?? 422;
const menuWidth = menuRef.current?.offsetWidth || MAIN_MENU_WIDTH;
const preferredRight = anchor.right + MENU_GAP;
const canOpenRight = preferredRight + MAIN_MENU_WIDTH <= window.innerWidth - VIEWPORT_PADDING;
const canOpenRight = preferredRight + menuWidth <= window.innerWidth - VIEWPORT_PADDING;
setMenuPosition({
top: clamp(anchor.top - 6, VIEWPORT_PADDING, window.innerHeight - measuredHeight - VIEWPORT_PADDING),
left: clamp(
canOpenRight ? preferredRight : anchor.left - MENU_GAP - MAIN_MENU_WIDTH,
canOpenRight ? preferredRight : anchor.left - MENU_GAP - menuWidth,
VIEWPORT_PADDING,
window.innerWidth - MAIN_MENU_WIDTH - VIEWPORT_PADDING,
window.innerWidth - menuWidth - VIEWPORT_PADDING,
),
});
}, []);
Expand Down Expand Up @@ -235,6 +236,7 @@ const WorkspaceSessionFilterMenu: React.FC = () => {
<Menu
ref={menuRef}
className="openbitfun-nav-panel__session-filter-menu"
inlineSize="content"
style={menuPosition}
autoFocusFirstItem
aria-label={t('nav.sessions.viewMenu.title')}
Expand Down Expand Up @@ -283,6 +285,7 @@ const WorkspaceSessionFilterMenu: React.FC = () => {
<Menu
ref={submenuRef}
className="openbitfun-nav-panel__session-filter-submenu"
inlineSize="content"
style={{
top: submenuPosition.top,
left: submenuPosition.left,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,7 @@ const SessionsSection: React.FC<SessionsSectionProps> = ({
<Menu
ref={sessionMenuPopoverRef}
className="openbitfun-nav-panel__inline-item-menu-popover"
inlineSize="content"
data-openbitfun-component="sessions-section"
data-openbitfun-part="menu"
data-openbitfun-state="menuOpen"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ const WorkspaceAcpSessionSubmenu = forwardRef<HTMLDivElement, WorkspaceAcpSessio
<Menu
ref={setSubmenuRef}
className="openbitfun-nav-panel__workspace-item-menu-popover openbitfun-nav-panel__workspace-acp-submenu"
inlineSize="content"
aria-label={label}
data-placement={layout?.placement}
data-testid="nav-workspace-menu-acp-submenu"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
<Menu
ref={menuPopoverRef}
className="openbitfun-nav-panel__workspace-item-menu-popover"
inlineSize="content"
style={{
top: menuPosition?.top ?? 0,
left: menuPosition?.left ?? 0,
Expand Down Expand Up @@ -1393,6 +1394,7 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
<Menu
ref={menuPopoverRef}
className="openbitfun-nav-panel__workspace-item-menu-popover"
inlineSize="content"
style={{
top: menuPosition?.top ?? 0,
left: menuPosition?.left ?? 0,
Expand Down
26 changes: 24 additions & 2 deletions src/web-ui/src/app/global-search/GlobalSearchRoot.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
@use '../styles/nav-panel-font-scope.scss' as nav-font;
@use '../styles/workspace-shell-surfaces' as shell-surfaces;

/*
* A lucide chevron inks only the middle of its square box -- the path spans
* x 9..15 in a 24 viewBox, so roughly 34% of the box stays empty on each side
* (about 5px at the 14-15px chevrons this surface renders). A pill whose
* chevron meets its inline padding has to cancel that slack, otherwise the
* chevron end reads wider than the text end even though the padding is even.
*/
$chevron-box-slack: 5px;

.global-search-dialog {
// The results viewport absorbs short windows and longer result sets.
inline-size: var(--openbitfun-layout-search-dialog-width);
Expand Down Expand Up @@ -136,10 +145,14 @@
* viewport no inline-start padding above 980px, so a leading negative
* margin crosses the clip line and chops the pill's leading radius.
*/
padding: 3px 6px 3px 4px;
padding: 3px 6px;
border-radius: 7px;
font-size: var(--openbitfun-type-label-sm-font-size);
font-weight: var(--openbitfun-type-label-sm-font-weight);

> [data-openbitfun-component='icon'] {
margin-inline-start: -$chevron-box-slack;
}
}

&__detail-heading {
Expand Down Expand Up @@ -592,14 +605,23 @@
font-weight: var(--openbitfun-type-body-sm-font-weight);
}

/*
* The hover pill paints its own background, and the results viewport has no
* inline-start padding to bleed a negative optical offset into, so the pill
* keeps real inline padding and its count sits inside the group title.
*/
.global-search__group-drilldown {
min-height: 0;
margin: 0;
padding: 0;
padding: 0 var(--openbitfun-space-1);
gap: var(--openbitfun-space-1);
color: inherit;
font-size: inherit;
font-weight: inherit;

> [data-openbitfun-component='icon'] {
margin-inline-end: -$chevron-box-slack;
}
}

.global-search__group-items {
Expand Down
2 changes: 1 addition & 1 deletion src/web-ui/src/font-profiles/apple-system.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
:where([data-openbitfun-design-system-root]) {
--openbitfun-font-family-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
--openbitfun-font-family-control: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
--openbitfun-font-family-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;
--openbitfun-font-family-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, "PingFang SC", "Hiragino Sans GB", monospace;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Navigation,
Scissors,
Square,
SquareCheckBig,
type LucideIcon,
} from 'lucide-react';

Expand Down Expand Up @@ -59,6 +60,7 @@ const CONTEXT_MENU_ICONS = {
List,
Navigation,
Scissors,
SelectAll: SquareCheckBig,
Square,
} satisfies Record<string, LucideIcon>;

Expand Down
Loading
Loading