Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter fields #286

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
33 changes: 33 additions & 0 deletions src/components/filter/SearchFilterField.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a story for these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import classNames from 'classnames'
import { forwardRef } from 'react'
import { FormVariant, SearchField, SearchFieldProps } from '../form'
import { FilterOptions, useFilter } from './useFilter'

export type SearchFilterFieldProps = SearchFieldProps & {
filter: {
value: HTMLInputElement['value']
setValue: (v: HTMLInputElement['value']) => void
options?: FilterOptions
}
}

export const SearchFilterField = forwardRef<
HTMLInputElement,
SearchFilterFieldProps
>(function SearchFilterField({ filter, className, ...props }, ref) {
const filterProps = useFilter<HTMLInputElement>()(
filter.value,
filter.setValue,
{ debounce: true, ...filter.options },
)

return (
<SearchField
{...filterProps}
variant={FormVariant.Soft}
className={classNames('w-full grow md:w-auto', className)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it is a good idea to hardcode the classes here.

What's your take on this @lukasvice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is what we always use according to our latest design specification

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but why not use the theme? Note that Tailwind is configured to look for class names only in theme files and not in component files!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. I didn't understand the comment. Of course I will put them in the theme file

{...props}
ref={ref}
/>
)
})
71 changes: 71 additions & 0 deletions src/components/filter/SelectFilterField.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a story for these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment above

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useMatchMediaQuery } from '@aboutbits/react-toolbox'
import classNames from 'classnames'
import { ForwardedRef, forwardRef } from 'react'
import { SelectFieldProps, SelectField, FormVariant } from '../form'
import { LoadingInput } from '../loading'
import { useTheme } from '../../framework'
import { FilterOptions, useFilter } from './useFilter'

export type HTMLElementWithValue = HTMLElement & {
value: unknown
}

export type FilterProps<
TElement extends HTMLElementWithValue,
TValue extends TElement['value'],
> = {
filter: {
value: TValue
setValue: (v: TValue) => void
options?: FilterOptions
}
}

export type SelectFilterFieldProps<TValue extends HTMLSelectElement['value']> =
SelectFieldProps & FilterProps<HTMLSelectElement, TValue>

function SelectFilterFieldComponent<TValue extends HTMLSelectElement['value']>(
{ filter, className, ...props }: SelectFilterFieldProps<TValue>,
ref: ForwardedRef<HTMLSelectElement>,
) {
const isScreenMedium = useMatchMediaQuery('(min-width: 768px)')

const filterProps = useFilter<HTMLSelectElement>()(
filter.value,
filter.setValue,
filter.options,
)

const {
filter: { select: theme },
} = useTheme()

return (
<SelectField
{...filterProps}
variant={isScreenMedium ? FormVariant.Transparent : FormVariant.Soft}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this we have no change to:

  1. Adjust screen sizes in a project
  2. Adjust the logic if the soft or transparent should be used for different screen size

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only the default. The variant can be overwritten to your liking

className={classNames(theme.normal, className)}
{...props}
ref={ref}
/>
)
}

/**
* A SelectField configured to be used for filtering a list.
*/
export const SelectFilterField = forwardRef(SelectFilterFieldComponent) as <
TValue extends HTMLSelectElement['value'],
>(
props: SelectFilterFieldProps<TValue> & {
ref?: ForwardedRef<HTMLSelectElement>
},
) => ReturnType<typeof SelectFilterFieldComponent>

export function LoadingSelectFilterField() {
const {
filter: { select: theme },
} = useTheme()

return <LoadingInput withLabel={false} className={theme.loading} />
}
8 changes: 8 additions & 0 deletions src/components/filter/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const className = {
select: {
normal: 'w-full md:w-auto',
loading: 'w-full md:w-40',
},
}

export default className
66 changes: 28 additions & 38 deletions src/examples/List.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { Meta, StoryFn } from '@storybook/react'
import { useMemo, useState } from 'react'
import { IndexType } from '@aboutbits/pagination'
import IconAdd from '@aboutbits/react-material-icons/dist/IconAdd'
import { useMatchMediaQuery } from '@aboutbits/react-toolbox'
import { Markdown } from '@storybook/addon-docs'
import {
ButtonIcon,
ButtonVariant,
FormVariant,
Section,
SectionContainer,
SectionContentEmpty,
Expand All @@ -24,12 +22,11 @@ import {
SectionHeaderSpacer,
SectionHeaderTitle,
SectionListItemButton,
SelectField,
Tone,
Option,
useFilter,
SearchField,
} from '../components'
import { SearchFilterField } from '../components/filter/SearchFilterField'
import { SelectFilterField } from '../components/filter/SelectFilterField'

const meta = {
component: SectionContentList,
Expand Down Expand Up @@ -102,10 +99,6 @@ export const EmptySimpleList: Story = () => <List numberOfTotalItems={0} />
* The following example shows how multiple section components and the in memory pagination are used to create an overview list with filters.
*/
export const ListWithFilter: Story = () => {
const isScreenMedium = useMatchMediaQuery('(min-width: 768px)')
const filterVariant = isScreenMedium
? FormVariant.Soft
: FormVariant.Transparent
const numberOfTotalItems = 1000
const numberOfItemsPerPage = 5
const [page, setPage] = useState(1)
Expand All @@ -116,24 +109,6 @@ export const ListWithFilter: Story = () => {
(filter.role === '' || item.role === filter.role) &&
(filter.department === '' || item.department === filter.department),
)
const searchFilterProps = useFilter<HTMLInputElement>()(
filter.search,
(v) => {
setFilter((prevFilter) => ({ ...prevFilter, search: v }))
},
{
debounce: true,
},
)
const roleFilterProps = useFilter<HTMLSelectElement>()(filter.role, (v) => {
setFilter((prevFilter) => ({ ...prevFilter, role: v }))
})
const departmentFilterProps = useFilter<HTMLSelectElement>()(
filter.department,
(v) => {
setFilter((prevFilter) => ({ ...prevFilter, department: v }))
},
)
return (
<Section>
<SectionHeaderArea>
Expand All @@ -152,30 +127,45 @@ export const ListWithFilter: Story = () => {
spacing={SectionHeaderGroupSpacing.Md}
className="flex-col gap-y-2 md:flex-row"
>
<SearchField
{...searchFilterProps}
className="w-full grow md:w-auto"
<SearchFilterField
filter={{
value: filter.search,
setValue: (v) => {
setFilter((prevFilter) => ({ ...prevFilter, search: v }))
},
}}
/>
<SectionHeaderGroup className="grid w-full grid-cols-2 md:flex md:w-auto">
<SelectField
{...roleFilterProps}
<SelectFilterField
name="role"
variant={filterVariant}
filter={{
value: filter.role,
setValue: (v) => {
setFilter((prevFilter) => ({ ...prevFilter, role: v }))
},
}}
>
<Option value="">All roles</Option>
<Option value="ADMIN">Admin</Option>
<Option value="USER">User</Option>
</SelectField>
<SelectField
{...departmentFilterProps}
</SelectFilterField>
<SelectFilterField
name="department"
variant={filterVariant}
filter={{
value: filter.department,
setValue: (v) => {
setFilter((prevFilter) => ({
...prevFilter,
department: v,
}))
},
}}
>
<Option value="">All departments</Option>
<Option value="HR">Human Resources</Option>
<Option value="IT">Engineering</Option>
<Option value="SALES">Sales</Option>
</SelectField>
</SelectFilterField>
</SectionHeaderGroup>
</SectionHeaderGroup>
</SectionHeaderRow>
Expand Down
2 changes: 2 additions & 0 deletions src/framework/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import menu from '../../components/menu/theme'
import pagination from '../../components/pagination/theme'
import section from '../../components/section/theme'
import tabs from '../../components/tabs/theme'
import filter from '../../components/filter/theme'

export const defaultTheme = {
action,
Expand All @@ -30,4 +31,5 @@ export const defaultTheme = {
menu,
pagination,
tabs,
filter,
}