Fix multi-select menus and selected label rendering
This commit is contained in:
1 parent
0941ea06f9
commit
cfe8020831
10 files changed
+142
-38
No files matched your search
@@ -0,0 +1,74 @@
|
|||||||
|
diff --git a/dist/index.js b/dist/index.js
|
||||||
|
index 2dd4992d4f033c22be13a8846637d48ef235c57f..cf5264a8ea0b6803b6fb9a950d35e7bd1ed0f959 100644
|
||||||
|
--- a/dist/index.js
|
||||||
|
+++ b/dist/index.js
|
||||||
|
@@ -6600,6 +6600,23 @@ function SearchField({
|
||||||
|
] })
|
||||||
|
] });
|
||||||
|
}
|
||||||
|
+function getSelectItemLabel(children, selectedValue) {
|
||||||
|
+ let label;
|
||||||
|
+ react.Children.forEach(children, (child) => {
|
||||||
|
+ if (label !== void 0 || !react.isValidElement(child)) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ if (child.type === ListItem) {
|
||||||
|
+ const itemValue = child.props.value ?? child.props.id ?? (typeof child.props.children === "string" ? child.props.children : "");
|
||||||
|
+ if (itemValue === selectedValue) {
|
||||||
|
+ label = child.props.children;
|
||||||
|
+ }
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ label = getSelectItemLabel(child.props.children, selectedValue);
|
||||||
|
+ });
|
||||||
|
+ return label;
|
||||||
|
+}
|
||||||
|
function Select({
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
@@ -6666,7 +6683,7 @@ function Select({
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
/* @__PURE__ */ jsxRuntime.jsx(select.Select.Value, { placeholder, children: (selected) => {
|
||||||
|
- const defaultChildren = selected ?? placeholder;
|
||||||
|
+ const defaultChildren = selected == null ? placeholder : getSelectItemLabel(collection, selected) ?? selected;
|
||||||
|
return typeof renderValue === "function" ? renderValue({
|
||||||
|
defaultChildren,
|
||||||
|
isPlaceholder: selected == null
|
||||||
|
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||||
|
index 448a673d0ef00587ba6a233d5e5f8e99efc8215e..f0c0f4e7a5a9f0e2066225e1598f944fbc033c6e 100644
|
||||||
|
--- a/dist/index.mjs
|
||||||
|
+++ b/dist/index.mjs
|
||||||
|
@@ -6575,6 +6575,23 @@ function SearchField({
|
||||||
|
] })
|
||||||
|
] });
|
||||||
|
}
|
||||||
|
+function getSelectItemLabel(children, selectedValue) {
|
||||||
|
+ let label;
|
||||||
|
+ Children.forEach(children, (child) => {
|
||||||
|
+ if (label !== void 0 || !isValidElement(child)) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ if (child.type === ListItem) {
|
||||||
|
+ const itemValue = child.props.value ?? child.props.id ?? (typeof child.props.children === "string" ? child.props.children : "");
|
||||||
|
+ if (itemValue === selectedValue) {
|
||||||
|
+ label = child.props.children;
|
||||||
|
+ }
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ label = getSelectItemLabel(child.props.children, selectedValue);
|
||||||
|
+ });
|
||||||
|
+ return label;
|
||||||
|
+}
|
||||||
|
function Select({
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
@@ -6641,7 +6658,7 @@ function Select({
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
/* @__PURE__ */ jsx(Select$1.Value, { placeholder, children: (selected) => {
|
||||||
|
- const defaultChildren = selected ?? placeholder;
|
||||||
|
+ const defaultChildren = selected == null ? placeholder : getSelectItemLabel(collection, selected) ?? selected;
|
||||||
|
return typeof renderValue === "function" ? renderValue({
|
||||||
|
defaultChildren,
|
||||||
|
isPlaceholder: selected == null
|
||||||
Generated
+5
-2
@@ -4,6 +4,9 @@ settings:
|
|||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
patchedDependencies:
|
||||||
|
'@umami/react-zen@0.247.0': 7a13e402d0a59d203f92fa4c64c8511aa38c5cd914092f5232c6271c606c9126
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
@@ -37,7 +40,7 @@ importers:
|
|||||||
version: 5.101.4(react@19.2.8)
|
version: 5.101.4(react@19.2.8)
|
||||||
'@umami/react-zen':
|
'@umami/react-zen':
|
||||||
specifier: ^0.247.0
|
specifier: ^0.247.0
|
||||||
version: 0.247.0(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(date-fns@4.4.0)(immer@11.1.15)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.8))
|
version: 0.247.0(patch_hash=7a13e402d0a59d203f92fa4c64c8511aa38c5cd914092f5232c6271c606c9126)(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(date-fns@4.4.0)(immer@11.1.15)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.8))
|
||||||
bcryptjs:
|
bcryptjs:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
@@ -8265,7 +8268,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/yargs-parser': 21.0.3
|
'@types/yargs-parser': 21.0.3
|
||||||
|
|
||||||
'@umami/react-zen@0.247.0(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(date-fns@4.4.0)(immer@11.1.15)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.8))':
|
'@umami/react-zen@0.247.0(patch_hash=7a13e402d0a59d203f92fa4c64c8511aa38c5cd914092f5232c6271c606c9126)(@date-fns/tz@1.5.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(date-fns@4.4.0)(immer@11.1.15)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.8))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@base-ui/react': 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
'@base-ui/react': 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
'@react-spring/web': 9.7.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
'@react-spring/web': 9.7.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
|
|||||||
@@ -12,3 +12,5 @@ allowBuilds:
|
|||||||
sharp: false
|
sharp: false
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- '@umami/react-zen@0.247.0'
|
- '@umami/react-zen@0.247.0'
|
||||||
|
patchedDependencies:
|
||||||
|
'@umami/react-zen@0.247.0': patches/@umami__react-zen@0.247.0.patch
|
||||||
@@ -51,7 +51,7 @@ export function TeamMemberAddForm({
|
|||||||
<UserSelect teamId={teamId} />
|
<UserSelect teamId={teamId} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField name="role" label={t(labels.role)} rules={{ required: 'Required' }}>
|
<FormField name="role" label={t(labels.role)} rules={{ required: 'Required' }}>
|
||||||
<Select renderValue={value => renderRole(value as any)}>
|
<Select>
|
||||||
{roles.map(value => (
|
{roles.map(value => (
|
||||||
<ListItem key={value} id={value}>
|
<ListItem key={value} id={value}>
|
||||||
{renderRole(value)}
|
{renderRole(value)}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Column, Grid, Label, ListItem, Loading, Select } from '@umami/react-zen
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import { MultiSelect } from '@/components/common/MultiSelect';
|
import { MultiSelect, MultiSelectItem } from '@/components/common/MultiSelect';
|
||||||
import { useDateRange, useMessages, useWebsiteValuesQuery } from '@/components/hooks';
|
import { useDateRange, useMessages, useWebsiteValuesQuery } from '@/components/hooks';
|
||||||
import { Attribution } from './Attribution';
|
import { Attribution } from './Attribution';
|
||||||
|
|
||||||
@@ -72,9 +72,9 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
|||||||
allowSearch
|
allowSearch
|
||||||
>
|
>
|
||||||
{items.map(({ value }) => (
|
{items.map(({ value }) => (
|
||||||
<ListItem key={value} id={value}>
|
<MultiSelectItem key={value} value={value}>
|
||||||
{value}
|
{value}
|
||||||
</ListItem>
|
</MultiSelectItem>
|
||||||
))}
|
))}
|
||||||
</MultiSelect>
|
</MultiSelect>
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import { MultiSelect } from '@/components/common/MultiSelect';
|
import { MultiSelect, MultiSelectItem } from '@/components/common/MultiSelect';
|
||||||
import { useFilters, useFormat, useWebsiteValuesQuery } from '@/components/hooks';
|
import { useFilters, useFormat, useWebsiteValuesQuery } from '@/components/hooks';
|
||||||
import { X } from '@/components/icons';
|
import { X } from '@/components/icons';
|
||||||
import { isSearchOperator } from '@/lib/params';
|
import { isSearchOperator } from '@/lib/params';
|
||||||
@@ -108,9 +108,9 @@ export function FilterRecord({
|
|||||||
allowSearch
|
allowSearch
|
||||||
>
|
>
|
||||||
{items.map(({ value }) => (
|
{items.map(({ value }) => (
|
||||||
<ListItem key={value} id={value}>
|
<MultiSelectItem key={value} value={value}>
|
||||||
{formatValue(value, type)}
|
{formatValue(value, type)}
|
||||||
</ListItem>
|
</MultiSelectItem>
|
||||||
))}
|
))}
|
||||||
</MultiSelect>
|
</MultiSelect>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,5 +35,5 @@ export function LinkButton({
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Button {...props} variant={variant} render={linkElement} />;
|
return <Button {...props} variant={variant} nativeButton={false} render={linkElement} />;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import { Button, Column, Icon, List, MenuTrigger, Popover, SearchField } from '@umami/react-zen';
|
import { Button, Icon, Menu, MenuItem, MenuTrigger, SearchField } from '@umami/react-zen';
|
||||||
import type { ReactNode } from 'react';
|
import { Children, type ReactNode } from 'react';
|
||||||
import { ChevronRight } from '@/components/icons';
|
import { ChevronRight } from '@/components/icons';
|
||||||
|
|
||||||
const listStyle = {
|
const listStyle = {
|
||||||
maxHeight: 'min(320px, calc(100dvh - 8rem))',
|
maxHeight: 'min(320px, calc(100dvh - 8rem))',
|
||||||
|
overflowY: 'auto' as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const menuItemProps = { closeOnClick: false };
|
||||||
|
|
||||||
interface MultiSelectProps {
|
interface MultiSelectProps {
|
||||||
value?: string[];
|
value?: string[];
|
||||||
onChange?: (values: string[]) => void;
|
onChange?: (values: string[]) => void;
|
||||||
@@ -18,6 +21,14 @@ interface MultiSelectProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function MultiSelectItem({ value, children }: { value: string; children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<MenuItem id={value} {...menuItemProps}>
|
||||||
|
{children}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function MultiSelect({
|
export function MultiSelect({
|
||||||
value = [],
|
value = [],
|
||||||
onChange,
|
onChange,
|
||||||
@@ -49,28 +60,35 @@ export function MultiSelect({
|
|||||||
<ChevronRight />
|
<ChevronRight />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover style={{ minWidth: 'var(--trigger-width)', maxWidth: 'var(--trigger-width)' }}>
|
<Menu
|
||||||
<Column
|
selectionMode="multiple"
|
||||||
gap="2"
|
selectedKeys={value}
|
||||||
padding="2"
|
onSelectionChange={keys =>
|
||||||
border
|
onChange?.(keys === 'all' ? [] : Array.from(keys, key => String(key)))
|
||||||
borderRadius="md"
|
}
|
||||||
shadow="lg"
|
className="flex flex-col gap-2"
|
||||||
className="bg-surface-overlay"
|
style={{
|
||||||
>
|
width: 'var(--anchor-width)',
|
||||||
{allowSearch && <SearchField value={searchValue} onSearch={onSearch} autoFocus />}
|
maxWidth: 'var(--available-width)',
|
||||||
<List
|
}}
|
||||||
selectionMode="multiple"
|
>
|
||||||
value={value}
|
{allowSearch && (
|
||||||
onChange={onChange}
|
<SearchField
|
||||||
showCheckmark
|
value={searchValue}
|
||||||
style={listStyle}
|
onSearch={onSearch}
|
||||||
renderEmptyState={renderEmptyState}
|
autoFocus
|
||||||
>
|
onKeyDown={event => {
|
||||||
{children}
|
if (event.key !== 'Escape' && event.key !== 'Tab') {
|
||||||
</List>
|
event.stopPropagation();
|
||||||
</Column>
|
}
|
||||||
</Popover>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div style={listStyle}>
|
||||||
|
{children}
|
||||||
|
{Children.count(children) === 0 && renderEmptyState?.()}
|
||||||
|
</div>
|
||||||
|
</Menu>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,14 @@ export function MetricsExpandedTable({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row alignItems="center" paddingBottom="3">
|
<Row alignItems="center" paddingBottom="3">
|
||||||
{allowSearch && <SearchField value={search} onSearch={setSearch} delay={300} />}
|
{allowSearch && (
|
||||||
|
<SearchField
|
||||||
|
value={search}
|
||||||
|
onSearch={setSearch}
|
||||||
|
delay={300}
|
||||||
|
className="w-full max-w-md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Row justifyContent="flex-end" flexGrow={1} gap>
|
<Row justifyContent="flex-end" flexGrow={1} gap>
|
||||||
{children}
|
{children}
|
||||||
{allowDownload && <DownloadButton filename={type} data={data} />}
|
{allowDownload && <DownloadButton filename={type} data={data} />}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useState } from 'react';
|
|||||||
import { ComboBox } from '@/components/common/ComboBox';
|
import { ComboBox } from '@/components/common/ComboBox';
|
||||||
import { DateDisplay } from '@/components/common/DateDisplay';
|
import { DateDisplay } from '@/components/common/DateDisplay';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import { MultiSelect } from '@/components/common/MultiSelect';
|
import { MultiSelect, MultiSelectItem } from '@/components/common/MultiSelect';
|
||||||
import { useMessages, usePropertyValuesQuery } from '@/components/hooks';
|
import { useMessages, usePropertyValuesQuery } from '@/components/hooks';
|
||||||
import type { PropertyDataSource } from '@/components/hooks/queries/usePropertyFieldsQuery';
|
import type { PropertyDataSource } from '@/components/hooks/queries/usePropertyFieldsQuery';
|
||||||
import { X } from '@/components/icons';
|
import { X } from '@/components/icons';
|
||||||
@@ -222,9 +222,9 @@ export function PropertyFilterRecord({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{filteredValues.map(v => (
|
{filteredValues.map(v => (
|
||||||
<ListItem key={v} id={v}>
|
<MultiSelectItem key={v} value={v}>
|
||||||
{v}
|
{v}
|
||||||
</ListItem>
|
</MultiSelectItem>
|
||||||
))}
|
))}
|
||||||
</MultiSelect>
|
</MultiSelect>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in new issue
Block a user