From 78a4b52a32c64f1efe77a4f1fb5926a059cde090 Mon Sep 17 00:00:00 2001 From: Gabriel Tiburcio Date: Tue, 12 Nov 2024 11:45:31 -0300 Subject: [PATCH 1/3] docs: complex table filters example --- .eslintrc.cjs | 1 + .storybook/preview.tsx | 2 +- docs/UX Patterns/Table/Filters.stories.tsx | 545 +++++++++++++-------- docs/UX Patterns/Table/Table.stories.tsx | 199 +------- docs/UX Patterns/Table/TableStoryUtils.tsx | 201 ++++++++ 5 files changed, 534 insertions(+), 414 deletions(-) create mode 100644 docs/UX Patterns/Table/TableStoryUtils.tsx diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ad9515584..b16e7e386 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -124,6 +124,7 @@ module.exports = { 'react/react-in-jsx-scope': 'off', 'import/no-duplicates': 'off', 'react/jsx-boolean-value': 'warn', + '@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }], }, globals: { React: true, diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index f82951939..421cd6054 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -38,7 +38,7 @@ const preview: Preview = { ], 'Candidate Components', 'UX Patterns', - ['Table', ['Table', 'Filters']], + ['Table', ['Table', ['Documentation', 'Cell Types', 'Basic Table'], 'Filters']], 'Contributing', ['Introduction', 'Commits', 'Testing in the platforms', 'Release Process', 'Maintainers'], ], diff --git a/docs/UX Patterns/Table/Filters.stories.tsx b/docs/UX Patterns/Table/Filters.stories.tsx index 8b3bac047..c994658c1 100644 --- a/docs/UX Patterns/Table/Filters.stories.tsx +++ b/docs/UX Patterns/Table/Filters.stories.tsx @@ -1,189 +1,33 @@ -import type { ReactNode } from 'react' -import { CopyOutlined } from '@ant-design/icons' -import { faker } from '@faker-js/faker' import type { Meta, StoryObj } from '@storybook/react' import { Flex, Icon, Input, - Select, - Badge, - type IBadgeProps, Table, - type TableProps, - Tag, - type ITagProps, - Typography, Space, - Tooltip, + Button, + Modal, + Typography, + Select, + Divider, + type ICollapseProps, + ConfigProvider, + Collapse, + Checkbox, } from 'src/components' import { DatePickerWithDisabledYears } from 'src/components/data-entry/DatePicker/DatePicker.stories' import { SelectWithRangePicker } from 'docs/Candidate Components/Directory/Date Range Filter/SelectWithRangePicker' - -interface DataType { - key: string - name: string - id: string - timestamp: number - output: string - environment: Environment - status: Status - mpId: string -} - -type Environment = 'unknown' | 'development' | 'production' -type Status = 'draft' | 'error' | 'ready' - -const EnvironmentColors: Record = { - production: 'blue', - development: 'purple', - unknown: 'default', -} - -const EnvironmentNames: Record = { - production: 'Prod', - development: 'Dev', - unknown: 'Unknown', -} - -const getTagColorForEnvironment = (env: Environment): ITagProps['color'] => EnvironmentColors[env] - -const getNameForEnvironment = (env: Environment) => EnvironmentNames[env] - -const StatusColors: Record = { - draft: 'cyan', - error: 'red', - ready: 'green', -} - -const StatusNames: Record = { - draft: 'Draft', - error: 'Error', - ready: 'Ready', -} - -const getStatusColor = (status: Status) => StatusColors[status] - -const getStatusName = (status: Status) => StatusNames[status] - -const columns: TableProps['columns'] = [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (name: string) => { - const path = window.location.pathname.split('/') - path.pop() - const route = `${path.join('/')}/${name}` - - return {name} - }, - }, - { - title: () => ( - - Help lorem ipsum. - - Learn More - - - }> - - ID - - - - ), - dataIndex: 'id', - key: 'id', - }, - { - title: 'Timestamp (UTC)', - dataIndex: 'timestamp', - key: 'timestamp', - render: (timestampInMicroseconds: number): string => { - return new Date(timestampInMicroseconds / (1000 * 1000)).toLocaleString(undefined, { - month: 'short', - day: '2-digit', - year: 'numeric', - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - timeZone: 'UTC', - hour12: false, - }) - }, - }, - { - title: 'mPID', - dataIndex: 'mpId', - key: 'mpId', - render: (mpId: string): ReactNode => { - return }}>{mpId} - }, - }, - { - title: 'Output', - dataIndex: 'output', - key: 'output', - }, - { - title: 'Environment', - key: 'environment', - dataIndex: 'environment', - render: (env: Environment): React.ReactNode => { - return {getNameForEnvironment(env)} - }, - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (status: Status): React.ReactNode => , - }, - { - title: 'Actions', - dataIndex: 'actions', - key: 'actions', - render: (): ReactNode => ( - + Recent first + Oldest first + + + + + + + + + + {/* + + */} + + + + + {/* + */} + } - placeholder="Search" - style={{ width: '240px' }} - /> - - - columns={columns} dataSource={data} scroll={{ x: 'max-content' }} /> - - ), + render: () => { + const [filters, setFilters] = useState(() => { + return { + ...DEFAULT_FILTERS, + } + }) + + const [showModal, context] = useModal({ + initialValues: filters, + onFinish: newFilters => { + setFilters(existing => ({ + ...existing, + ...newFilters, + })) + }, + }) + + return ( + <> + {context} + + + + + onUpdateFilters({ time })} + rangePickerProps={{ + showTime: true, + showHour: true, + showMinute: true, + showSecond: false, + disabledDate: antdDayJS => { + const fourteenDaysInMs = 14 * 24 * 60 * 60 * 1000 + return antdDayJS.isBefore(new Date(Date.now() - fourteenDaysInMs)) + }, + }} + /> + + + } + placeholder="Search" + style={{ width: '240px' }} + /> + + + columns={tableColumns} dataSource={tableData} scroll={{ x: 'max-content' }} /> + + + ) + }, } diff --git a/docs/UX Patterns/Table/Table.stories.tsx b/docs/UX Patterns/Table/Table.stories.tsx index bb0f6eb45..aaeb07f45 100644 --- a/docs/UX Patterns/Table/Table.stories.tsx +++ b/docs/UX Patterns/Table/Table.stories.tsx @@ -1,200 +1,7 @@ -import type { ReactNode } from 'react' -import { CopyOutlined } from '@ant-design/icons' -import { faker } from '@faker-js/faker' import type { Meta, StoryObj } from '@storybook/react' -import { - Flex, - Icon, - Input, - Select, - Badge, - type IBadgeProps, - Table, - type TableProps, - Tag, - type ITagProps, - Typography, - Space, - Tooltip, -} from 'src/components' +import { Flex, Icon, Input, Table, Space } from 'src/components' import { DatePickerWithDisabledYears } from 'src/components/data-entry/DatePicker/DatePicker.stories' -import { ColorError, ColorSuccess, ColorTextPlaceholder } from 'src/styles/style' - -interface DataType { - key: string - name: string - id: string - timestamp: number - output: string - environment: Environment - status: Status - mpId: string -} - -type Environment = 'development' | 'production' -type Status = 'draft' | 'error' | 'ready' - -const EnvironmentColors: Record = { - production: 'blue', - development: 'purple', -} - -const EnvironmentNames: Record = { - production: 'Prod', - development: 'Dev', -} - -const getTagColorForEnvironment = (env: Environment): ITagProps['color'] => EnvironmentColors[env] - -const getNameForEnvironment = (env: Environment) => EnvironmentNames[env] - -const StatusColors: Record = { - draft: ColorTextPlaceholder, - error: ColorError, - ready: ColorSuccess, -} - -const StatusNames: Record = { - draft: 'Draft', - error: 'Error', - ready: 'Ready', -} - -const getStatusColor = (status: Status) => StatusColors[status] - -const getStatusName = (status: Status) => StatusNames[status] - -const columns: TableProps['columns'] = [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (name: string) => { - const path = window.location.pathname.split('/') - path.pop() - const route = `${path.join('/')}/${name}` - - return {name} - }, - }, - { - title: () => ( - - ID - - Help lorem ipsum. - - Learn More - - - }> - - - - ), - dataIndex: 'id', - key: 'id', - }, - { - title: 'Timestamp (UTC)', - dataIndex: 'timestamp', - key: 'timestamp', - render: (timestampInMicroseconds: number): string => { - return new Date(timestampInMicroseconds / (1000 * 1000)).toLocaleString(undefined, { - month: 'short', - day: '2-digit', - year: 'numeric', - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - timeZone: 'UTC', - hour12: false, - }) - }, - }, - { - title: 'mPID', - dataIndex: 'mpId', - key: 'mpId', - render: (mpId: string): ReactNode => { - return }}>{mpId} - }, - }, - { - title: 'Output', - dataIndex: 'output', - key: 'output', - }, - { - title: 'Environment', - key: 'environment', - dataIndex: 'environment', - render: (env: Environment): React.ReactNode => { - return {getNameForEnvironment(env)} - }, - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (status: Status): React.ReactNode => , - }, - { - title: 'Actions', - dataIndex: 'actions', - key: 'actions', - render: (): ReactNode => ( - } + variant="borderless" + dropdownStyle={{ width: '200px' }} + value={null} + options={[ + { label: 'Option 1', value: 'option1' }, + { + label: ( + + Option 2 + + ), + value: 'option2', + disabled: true, + }, + { + label: Delete, + value: 'option2', + }, + ]} + /> + ), + }, +] + +function createMockRow(): TableDataType { + return { + id: `JNBSK-${faker.number.int({ min: 1000, max: 9999 }).toString()}`, + key: faker.string.uuid(), + name: faker.helpers.arrayElement([ + 'NBCU', + 'Remarkable Foods', + 'Lulo Bank', + 'Shift', + 'Marks and Spencer', + "Zaxby's", + ]), + timestamp: faker.date.recent().valueOf() * 1000 * 1000, + mpId: faker.number.int({ max: 9_999_999_999 }).toString(), + output: faker.helpers.arrayElement(['Braze', 'mP Analytics', 'Cortex', 'Applytics', 'Google Analytics']), + environment: faker.helpers.arrayElement(['development', 'production']), + status: faker.helpers.arrayElement(['draft', 'error', 'ready']), + } +} + +export interface TableDataTypeFilters { + sortAscending?: boolean + id?: string + name?: string + mpId?: string + output?: Array<'Braze' | 'mP Analytics' | 'Cortex' | 'Applytics' | 'Google Analytics'> + environments?: Environment[] + statuses?: Status[] +} + +export const DEFAULT_FILTERS: TableDataTypeFilters = { + sortAscending: false, + name: undefined, + mpId: undefined, + output: undefined, + environments: ['development', 'production'], + statuses: ['draft', 'error', 'ready'], +} as const + +export const tableData: TableDataType[] = faker.helpers.multiple(createMockRow, { + count: 45, +}) From 0f29598a9b5bb379fb25da88bce796d0210b7a97 Mon Sep 17 00:00:00 2001 From: Gabriel Tiburcio Date: Wed, 13 Nov 2024 13:48:07 -0300 Subject: [PATCH 2/3] docs: table complex filters fixes --- .storybook/preview.tsx | 9 +- docs/UX Patterns/Table/Filters.mdx | 14 +- docs/UX Patterns/Table/Filters.stories.tsx | 389 ------------------ docs/UX Patterns/Table/Table.stories.tsx | 313 +++++++++++++- .../data-display/Collapse/Collapse.tsx | 2 +- 5 files changed, 324 insertions(+), 403 deletions(-) delete mode 100644 docs/UX Patterns/Table/Filters.stories.tsx diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 421cd6054..41d49813b 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,9 +1,16 @@ import type { Preview } from '@storybook/react' +// import type { IndexEntry } from '@storybook/types' const preview: Preview = { parameters: { layout: 'centered', options: { + // TODO https://mparticle-eng.atlassian.net/browse/UNI-1214 + // storySort: (a: IndexEntry, b: IndexEntry) => { + // console.log('Ordering stories', { a, b }) + // const order = ['Documentation', 'Cell Types', 'Filters', 'Primary', 'Complex'] + // return order.indexOf(a[1].name) - order.indexOf(b[1].name) + // }, storySort: { order: [ 'About', @@ -38,7 +45,7 @@ const preview: Preview = { ], 'Candidate Components', 'UX Patterns', - ['Table', ['Table', ['Documentation', 'Cell Types', 'Basic Table'], 'Filters']], + ['Table', ['Table', ['Documentation', 'Cell Types', 'Filters', 'Primary', 'Complex']]], 'Contributing', ['Introduction', 'Commits', 'Testing in the platforms', 'Release Process', 'Maintainers'], ], diff --git a/docs/UX Patterns/Table/Filters.mdx b/docs/UX Patterns/Table/Filters.mdx index 7d3eae893..2b997dbe5 100644 --- a/docs/UX Patterns/Table/Filters.mdx +++ b/docs/UX Patterns/Table/Filters.mdx @@ -1,8 +1,8 @@ import { Meta, Story } from '@storybook/blocks' -import * as FiltersStories from './Filters.stories' +import * as TableStories from './Table.stories' - + # Filters @@ -11,24 +11,20 @@ import * as FiltersStories from './Filters.stories' _Note: This section covers filters specifically for tables. For query-related filters, please refer to the [Analytics Filters - Coming Soon](#coming)._ #### Filter Search + Located above the table on the right, the search filter allows users to quickly find specific data within the table by entering keywords. #### **Simple Filters** Simple filters are ideal when there are only a few filter options. These are straightforward and quick to use and appearing as dropdowns above the table. For examples, refer to the [Select Component](https://mparticle.github.io/aquarium/?path=/docs/components-data-entry-select--documentation). -Examples: - - - #### **Filters with Apply Button** Complex filters provide more advanced filtering options, allowing users to apply multiple criteria at once. These filters often include dropdowns, checkboxes, and text fields. Complex filters are particularly useful when multiple filters need to be applied simultaneously or when load times might be a concern. -- daterange -- modal: sorting, filters: one of each input type: checkboxes, input, tree select and placeholder for tags +Example: - + #### **Date Range Filters** diff --git a/docs/UX Patterns/Table/Filters.stories.tsx b/docs/UX Patterns/Table/Filters.stories.tsx deleted file mode 100644 index c994658c1..000000000 --- a/docs/UX Patterns/Table/Filters.stories.tsx +++ /dev/null @@ -1,389 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { - Flex, - Icon, - Input, - Table, - Space, - Button, - Modal, - Typography, - Select, - Divider, - type ICollapseProps, - ConfigProvider, - Collapse, - Checkbox, -} from 'src/components' -import { DatePickerWithDisabledYears } from 'src/components/data-entry/DatePicker/DatePicker.stories' -import { SelectWithRangePicker } from 'docs/Candidate Components/Directory/Date Range Filter/SelectWithRangePicker' -import { useState } from 'react' -import { - DEFAULT_FILTERS, - tableColumns, - tableData, - type TableDataType, - type TableDataTypeFilters, -} from './TableStoryUtils' -import { ColorTextDescription } from 'src/styles/style' -import MinusSquareOutlined from '@ant-design/icons/MinusSquareOutlined' -import PlusSquareOutlined from '@ant-design/icons/PlusSquareOutlined' - -const meta: Meta = { - title: 'UX Patterns/Table/Filters', - component: Table, - - args: {}, -} - -export default meta - -type Story = StoryObj - -export const WithBasicFilters: Story = { - name: 'Basic', - render: () => ( - - - - - - - } - placeholder="Search" - style={{ width: '240px' }} - /> - - - columns={tableColumns} dataSource={tableData} scroll={{ x: 'max-content' }} /> - - ), -} - -const TIME_OPTIONS = [ - { - value: 'last12hours', - label: 'Last 12 hours', - } as const, - { - value: 'last7days', - label: 'Last 7 days', - } as const, - { - value: 'last14days', - label: 'Last 14 days', - } as const, -] - -interface IUseSortAndFiltersModalProps { - initialValues: TableDataTypeFilters - onFinish: (values: TableDataTypeFilters) => void -} - -const FormSectionHeader = ({ label }: { label: string }) => ( - - {label} - -) - -interface IMultipleCheckboxItem { - name: string - value: unknown -} - -interface IMultipleCheckboxesProps { - label: string - fieldName: string - items: IMultipleCheckboxItem[] - defaultOpen?: boolean -} - -type ICollapsibleFormSectionProps = ICollapseProps['items'][0] & { - defaultOpen?: boolean -} - -const CollapsibleFormSection = ({ defaultOpen, ...item }: ICollapsibleFormSectionProps): React.JSX.Element => { - return ( - - (isActive ? : )} - items={[{ ...item, key: 'item' }]} - /> - - ) -} - -const MultipleCheckboxes = ({ label, fieldName, items, defaultOpen }: IMultipleCheckboxesProps) => ( - prev[fieldName] !== next[fieldName]}> - // {({ getFieldValue }) => { - // const selected = getFieldValue(fieldName) ?? [] - // if (!selected.length) { - // return null - // } - // return ( - // - // {selected.length} selected - // - // ) - // }} - // - - 1 selected - - }> - {/* prev[fieldName] !== next[fieldName]}> - {({ getFieldValue }) => { - const fieldValue = getFieldValue(fieldName) ?? [] - - return ( - - {items.map(({ name, value }) => { - return ( - ({ - checked: currValue.includes(value), - })} - getValueFromEvent={e => { - if (e.target.checked) { - return [...fieldValue, value] - } - - return fieldValue.filter((currValue: unknown) => currValue !== value) - }} - noStyle> - {name} - - ) - })} - - ) - }} - */} - - {items.map(({ name, value }) => ( - {name} - ))} - - -) - -function useModal({ initialValues, onFinish }: IUseSortAndFiltersModalProps): [() => void, React.ReactElement] { - const [open, setOpen] = useState(false) - const [openSection, setOpenSection] = useState() - // const [form] = Form.useForm() - - const context = ( - { - setOpen(false) - }} - cancelText={'Cancel'} - // onOk={form.submit} - onOk={() => setOpen(false)} - okText={'Apply'} - width={'580px'} - style={{ maxHeight: '80vh' }} - closable - title={ - - Sort & Filters - - }> - {/* - // form={form} - layout="horizontal" - colon={false} - labelAlign="left" - labelCol={{ span: 12 }} - wrapperCol={{ span: 12 }} - initialValues={initialValues} - onFinish={values => { - console.log('values', values) - setOpen(false) - onFinish(values) - }}> */} - - - {/* name={'sortAscending'} label={'Order'}> - - */} - - - - - - - - - - {/* - - */} - - - - - {/* - */} - } - placeholder="Search" - style={{ width: '240px' }} - /> - - - columns={tableColumns} dataSource={tableData} scroll={{ x: 'max-content' }} /> - - - ) - }, -} diff --git a/docs/UX Patterns/Table/Table.stories.tsx b/docs/UX Patterns/Table/Table.stories.tsx index aaeb07f45..f613af818 100644 --- a/docs/UX Patterns/Table/Table.stories.tsx +++ b/docs/UX Patterns/Table/Table.stories.tsx @@ -1,7 +1,30 @@ import type { Meta, StoryObj } from '@storybook/react' -import { Flex, Icon, Input, Table, Space } from 'src/components' +import { + Flex, + Icon, + Input, + Table, + Space, + Button, + Checkbox, + Collapse, + ConfigProvider, + Divider, + type ICollapseProps, + Modal, + Select, + Typography, + TreeSelect, + Row, + Col, +} from 'src/components' import { DatePickerWithDisabledYears } from 'src/components/data-entry/DatePicker/DatePicker.stories' import { tableColumns, tableData, type TableDataType } from './TableStoryUtils' +import { SelectWithRangePicker } from 'docs/Candidate Components/Directory/Date Range Filter/SelectWithRangePicker' +import { useState } from 'react' +import { ColorTextDescription } from 'src/styles/style' +import MinusSquareOutlined from '@ant-design/icons/MinusSquareOutlined' +import PlusSquareOutlined from '@ant-design/icons/PlusSquareOutlined' const meta: Meta = { title: 'UX Patterns/Table/Table', @@ -14,8 +37,7 @@ export default meta type Story = StoryObj -export const BasicTable: Story = { - name: 'Basic Table', +export const Primary: Story = { render: () => ( @@ -35,3 +57,288 @@ export const BasicTable: Story = { ), } + +const TIME_OPTIONS = [ + { + value: 'last12hours', + label: 'Last 12 hours', + } as const, + { + value: 'last7days', + label: 'Last 7 days', + } as const, + { + value: 'last14days', + label: 'Last 14 days', + } as const, +] + +const FormSectionHeader = ({ label }: { label: string }) => ( + + {label} + +) + +interface IMultipleCheckboxItem { + name: string + value: unknown +} + +interface IMultipleCheckboxesProps { + label: string + fieldName: string + items: IMultipleCheckboxItem[] +} + +type ICollapsibleFormSectionProps = NonNullable[0] + +const CollapsibleSection = ({ ...item }: ICollapsibleFormSectionProps): React.JSX.Element => { + return ( + + (isActive ? : )} + items={[{ ...item, key: 'item' }]} + /> + + ) +} + +const MultipleCheckboxes = ({ label, items }: IMultipleCheckboxesProps) => ( + + 1 selected + + }> + + {items.map(({ name, value }) => ( + {name} + ))} + + +) + +function useModal(): [() => void, React.ReactElement] { + const [open, setOpen] = useState(false) + + const context = ( + { + setOpen(false) + }} + cancelText={'Cancel'} + onOk={() => setOpen(false)} + okText={'Apply'} + width={'580px'} + style={{ maxHeight: '80vh' }} + closable + title={ + + Sort & Filters + + }> + + + + + Order + + + + + + + + + + + + + + + + + + } + placeholder="Search" + style={{ width: '240px' }} + /> + + + + columns={tableColumns} + dataSource={tableData.slice(0, 2)} + scroll={{ x: 'max-content' }} + /> + + + ) + }, +} diff --git a/src/components/data-display/Collapse/Collapse.tsx b/src/components/data-display/Collapse/Collapse.tsx index 52146a9ea..a59fd6793 100644 --- a/src/components/data-display/Collapse/Collapse.tsx +++ b/src/components/data-display/Collapse/Collapse.tsx @@ -1,5 +1,5 @@ import { Collapse as AntCollapse } from 'antd' -import { type CollapseProps as AntCollapseProps } from 'antd' +import type { CollapseProps as AntCollapseProps } from 'antd' import { ConfigProvider } from 'src/components' export interface ICollapseProps extends AntCollapseProps {} From 974ef997a2f7996eb39bd5c4588fbbf6884d6a67 Mon Sep 17 00:00:00 2001 From: Gabriel Tiburcio Date: Wed, 13 Nov 2024 14:32:13 -0300 Subject: [PATCH 3/3] docs: fix compilation errors --- docs/UX Patterns/Table/Table.mdx | 6 +----- docs/UX Patterns/Table/Table.stories.tsx | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/UX Patterns/Table/Table.mdx b/docs/UX Patterns/Table/Table.mdx index 33dacf04a..9ad581a53 100644 --- a/docs/UX Patterns/Table/Table.mdx +++ b/docs/UX Patterns/Table/Table.mdx @@ -29,7 +29,6 @@ A table row with expand and collapse functionality, allowing users to toggle add -> Insert Example - ### Pagination Use pagination in tables to improve performance and reduce load times by fetching only the data needed for the current page. @@ -40,14 +39,11 @@ Use pagination in tables to improve performance and reduce load times by fetchin Filters help users narrow down large datasets within tables. Learn more about the variety of filter types available in the mParticle table [here.](https://mparticle.github.io/aquarium/?path=/docs/ux-patterns-table-filters--documentation) - ### Table Example
- - - + ### Related Links diff --git a/docs/UX Patterns/Table/Table.stories.tsx b/docs/UX Patterns/Table/Table.stories.tsx index f613af818..426c28137 100644 --- a/docs/UX Patterns/Table/Table.stories.tsx +++ b/docs/UX Patterns/Table/Table.stories.tsx @@ -175,7 +175,9 @@ function useModal(): [() => void, React.ReactElement] { showSearch style={{ width: '100%' }} placeholder="Select name" - filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())} + filterOption={(input, option) => + typeof option?.label === 'string' && option.label.toLowerCase().includes(input.toLowerCase()) + } options={[ { value: 'NBCU', label: 'NBCU' }, { value: 'Remarkable Foods', label: 'Remarkable Foods' },