From 6d70a9b8a364c111589823c4f7d1e062d526e3fd Mon Sep 17 00:00:00 2001 From: SerhiiTsybulskyi Date: Mon, 27 May 2024 14:59:59 +0300 Subject: [PATCH 1/6] dark theme for select --- src/form/Select/Select.tsx | 8 + src/form/Select/SelectInput/SelectInput.tsx | 192 +++++++++--------- .../Select/SelectInput/SelectInputTheme.ts | 48 ++++- src/form/Select/SelectMenu/SelectMenu.tsx | 39 +++- src/form/Select/SelectMenu/SelectMenuTheme.ts | 57 +++++- src/form/Select/SingleSelect.story.tsx | 62 ++++++ src/form/Select/icons/CheckIcon.tsx | 23 +++ src/form/Select/icons/CloseIcon.tsx | 9 +- src/form/Select/icons/DownArrowIcon.tsx | 11 +- src/form/Select/icons/RefreshIcon.tsx | 5 +- src/layout/List/ListItem/ListItem.tsx | 22 +- src/layout/List/ListTheme.ts | 2 +- tailwind.config.ts | 4 +- 13 files changed, 343 insertions(+), 139 deletions(-) create mode 100644 src/form/Select/icons/CheckIcon.tsx diff --git a/src/form/Select/Select.tsx b/src/form/Select/Select.tsx index 656c70bb..a8c2b328 100644 --- a/src/form/Select/Select.tsx +++ b/src/form/Select/Select.tsx @@ -151,6 +151,11 @@ export interface SelectProps { */ menuDisabled?: boolean; + /** + * The size of the select. + */ + size?: 'small' | 'medium' | 'large' | string; + /** * When the input receives a key down event. */ @@ -244,6 +249,7 @@ export const Select: FC = ({ value, defaultFilterValue, required, + size = 'medium', input = , menu = , onRefresh, @@ -747,6 +753,7 @@ export const Select: FC = ({ inputSearchText={keyword} loading={loading} filterable={filterable} + size={size} onSelectedChange={onMenuSelectedChange} /> )} @@ -775,6 +782,7 @@ export const Select: FC = ({ selectedOption={selectedOption} clearable={clearable} menuDisabled={menuDisabled} + size={size} onSelectedChange={toggleSelectedOption} onExpandClick={onInputExpanded} onKeyDown={onInputKeyedDown} diff --git a/src/form/Select/SelectInput/SelectInput.tsx b/src/form/Select/SelectInput/SelectInput.tsx index 22b94344..3264a8ed 100644 --- a/src/form/Select/SelectInput/SelectInput.tsx +++ b/src/form/Select/SelectInput/SelectInput.tsx @@ -16,7 +16,7 @@ import { DotsLoader } from '@/elements/Loader/DotsLoader'; import { RefreshIcon } from '@/form/Select/icons/RefreshIcon'; import { SelectInputChip, SelectInputChipProps } from './SelectInputChip'; import { twMerge } from 'tailwind-merge'; -import { useComponentTheme } from '@/utils'; +import { cn, useComponentTheme } from '@/utils'; import { SelectTheme } from '@/form/Select/SelectTheme'; import { CloneElement } from '@/utils'; @@ -131,6 +131,11 @@ export interface SelectInputProps { */ menuDisabled?: boolean; + /** + * The size of the select input. + */ + size?: 'small' | 'medium' | 'large' | string; + /** * The theme of the select input. */ @@ -250,6 +255,7 @@ export const SelectInput: FC = ({ error, menuDisabled, menuOpen, + size, refreshIcon = , closeIcon = , expandIcon = , @@ -488,101 +494,105 @@ export const SelectInput: FC = ({ ]); return ( -
+
- {renderPrefix()} - -
-
- {refreshable && !loading && ( - - )} - {loading &&
{loadingIcon}
} - {showClear && ( - - )} - {!menuDisabled && ( - - )} + value={inputTextValue} + autoCorrect="off" + spellCheck="false" + autoComplete="off" + onKeyDown={onInputKeyDown} + onKeyUp={onInputKeyUp} + onChange={onChange} + onFocus={onInputFocus} + onBlur={onBlur} + onPaste={onPaste} + placeholderIsMinWidth={false} + /> +
+
+ {refreshable && !loading && ( + + )} + {loading &&
{loadingIcon}
} + {showClear && ( + + )} + {!menuDisabled && ( + + )} +
); diff --git a/src/form/Select/SelectInput/SelectInputTheme.ts b/src/form/Select/SelectInput/SelectInputTheme.ts index 8095b283..19d49d1d 100644 --- a/src/form/Select/SelectInput/SelectInputTheme.ts +++ b/src/form/Select/SelectInput/SelectInputTheme.ts @@ -1,4 +1,5 @@ export interface SelectInputTheme { + container: string; base: string; inputContainer: string; input: string; @@ -32,14 +33,21 @@ export interface SelectInputTheme { disabled: string; removeButton: string; }; + size: { + small: string; + medium: string; + large: string; + [key: string]: string; + }; } const baseTheme: SelectInputTheme = { - base: 'flex flex-nowrap items-center box-border border rounded py-1.5 px-3 ', + base: 'flex flex-nowrap items-center box-border border rounded', + container: 'relative', inputContainer: 'flex-wrap flex items-center overflow-hidden flex-1 max-w-full [&>div]:max-w-full', input: - 'p-0 bg-transparent text-base text-ellipsis align-middle max-w-full read-only:cursor-not-allowed focus:outline-none disabled:text-disabled', + 'p-0 bg-transparent text-ellipsis align-middle max-w-full read-only:cursor-not-allowed focus:outline-none disabled:text-disabled', placeholder: '', prefix: 'overflow-hidden whitespace-nowrap text-ellipsis', suffix: { @@ -52,7 +60,7 @@ const baseTheme: SelectInputTheme = { 'mr-1.5 [&>svg]:w-4 [&>svg]:h-4 [&>svg]:fill-panel-secondary-content', expand: '[&>svg]:w-4 [&>svg]:h-4 [&>svg]:fill-panel-secondary-content' }, - disabled: 'cursor-not-allowed text-disabled', + disabled: 'cursor-not-allowed text-disabled hover:after:content-none', unfilterable: 'caret-transparent', error: 'border border-solid', open: 'rounded rounded-ee-none rounded-es-none', @@ -72,6 +80,11 @@ const baseTheme: SelectInputTheme = { disabled: 'disabled:cursor-not-allowed', removeButton: 'cursor-pointer leading-[0] ml-1 p-0 border-0 [&>svg]:w-3 [&>svg]:h-3 [&>svg]:align-baseline [&>svg]:pointer-events-none' + }, + size: { + small: 'py-1 px-2 text-sm min-h-8', + medium: 'py-2 px-3 text-base min-h-[35px]', + large: 'py-2 px-3 text-lg min-h-[42px]' } }; @@ -79,20 +92,37 @@ export const selectInputTheme: SelectInputTheme = { ...baseTheme, base: [ baseTheme.base, - 'bg-panel text-panel-content border-panel-accent border-solid' + 'bg-panel text-panel-content border-panel-accent border-solid hover:border-panel-accent', + // 'hover:after:bg-input-hover focus-within:after:bg-input-focus', + 'hover:after:bg-[radial-gradient(circle,_#105EFF_0%,_#105EFF_36%,_#242433_100%)]', + 'focus-within:after:bg-[radial-gradient(circle,_#93B6FF_0%,_#105EFF_36%,_#3D3D4D 90%,_#242433_100%)]', + 'hover:after:content-[""] hover:after:absolute hover:after:mx-1 hover:after:h-px after:z-[2] hover:after:rounded hover:after:-bottom-[0px] hover:after:inset-x-0.5', + 'focus-within:after:content-[""] focus-within:after:absolute focus-within:after:mx-0 focus-within:after:h-px after:z-[2] focus-within:after:rounded focus-within:after:-bottom-[0px] focus-within:after:inset-x-0.5' + ].join(' '), + placeholder: [ + baseTheme.placeholder, + 'placeholder:text-secondary-content' + ].join(' '), + disabled: [ + baseTheme.disabled, + 'text-panel-secondary-content/40 border-surface' ].join(' '), - disabled: [baseTheme.disabled, 'opacity-75'].join(' '), error: [baseTheme.error, 'border-error'].join(' '), + suffix: { + ...baseTheme.suffix, + button: [baseTheme.suffix.button, 'hover:cursor-pointer'].join(' ') + }, chip: { ...baseTheme.chip, - base: [baseTheme.chip.base, 'bg-panel-accent text-surface-content'].join( - ' ' - ), + base: [ + baseTheme.chip.base, + '[&>svg]:fill-panel-content disabled:[&>svg]:fill-panel-secondary-content/40' + ].join(' '), hover: [baseTheme.chip.hover, 'hover:brightness-150'].join(' '), focused: [baseTheme.chip.focused, 'border-panel-accent'].join(' '), removeButton: [ baseTheme.chip.removeButton, - '[&>svg]:fill-panel-content' + '[&>svg]:fill-panel-content disabled:[&>svg]:fill-panel-secondary-content/40' ].join(' ') } }; diff --git a/src/form/Select/SelectMenu/SelectMenu.tsx b/src/form/Select/SelectMenu/SelectMenu.tsx index 855bf590..ab7b04d3 100644 --- a/src/form/Select/SelectMenu/SelectMenu.tsx +++ b/src/form/Select/SelectMenu/SelectMenu.tsx @@ -4,9 +4,9 @@ import { SelectOptionProps, SelectValue } from '@/form/Select/SelectOption'; import Highlighter from 'react-highlight-words'; import { GroupOptions, GroupOption } from '@/form/Select/utils'; import { List, ListItem } from '@/layout'; -import { useComponentTheme } from '@/utils'; +import { cn, useComponentTheme } from '@/utils'; import { SelectTheme } from '@/form/Select/SelectTheme'; -import { twMerge } from 'tailwind-merge'; +import { CheckIcon } from '@/form/Select/icons/CheckIcon'; export interface SelectMenuProps { /** @@ -74,6 +74,11 @@ export interface SelectMenuProps { */ loading?: boolean; + /** + * The size of the select menu. + */ + size?: 'small' | 'medium' | 'large' | string; + /** * Event fired when the selected option(s) change. */ @@ -98,6 +103,7 @@ export const SelectMenu: FC = ({ groups, multiple, inputSearchText, + size, onSelectedChange, theme: customTheme }) => { @@ -128,13 +134,17 @@ export const SelectMenu: FC = ({ items.map((o, i) => ( { event.preventDefault(); event.stopPropagation(); @@ -150,6 +160,9 @@ export const SelectMenu: FC = ({ textToHighlight={o.children} /> )} + {Boolean(multiple && checkOptionSelected(o)) && ( + + )} )), [ @@ -157,15 +170,18 @@ export const SelectMenu: FC = ({ disabled, index, inputSearchText, + size, + multiple, onSelectedChange, - theme.option + theme.option, + theme.size ] ); return ( = ({ renderListItems(g.items, g) ) : (

diff --git a/src/form/Select/SelectMenu/SelectMenuTheme.ts b/src/form/Select/SelectMenu/SelectMenuTheme.ts index d3b5c6a4..8631605c 100644 --- a/src/form/Select/SelectMenu/SelectMenuTheme.ts +++ b/src/form/Select/SelectMenu/SelectMenuTheme.ts @@ -1,26 +1,56 @@ export interface SelectMenuTheme { base: string; - groupItem: string; - groupTitle: string; + groupItem: { + base: string; + title: string; + size: { + small: string; + medium: string; + large: string; + [key: string]: string; + }; + }; option: { base: string; hover: string; selected: string; active: string; disabled: string; + checkIcon: string; + content: string; + }; + size: { + small: string; + medium: string; + large: string; + [key: string]: string; }; } const baseTheme: SelectMenuTheme = { base: 'border border-solid rounded-b-md text-center will-change-[transform,opacity] min-w-[112px] max-h-[300px] overflow-y-auto text-left box-border', - groupItem: 'p-0 border-0', - groupTitle: 'text-sm font-bold uppercase m-0 px-1.5 py-2.5', + groupItem: { + base: 'p-0 border-0 first:pt-2 last:pb-2', + title: 'font-bold uppercase m-0 px-1.5 py-2.5', + size: { + small: 'px-2.5 text-sm', + medium: 'px-3 text-sm', + large: 'px-3.5 text-base' + } + }, option: { - base: 'text-sm flex-1 whitespace-break-spaces break-words py-1.5 px-2.5', + base: 'flex-1 whitespace-break-spaces break-words py-1.5 px-2.5', hover: '', selected: '', active: '', - disabled: '' + disabled: '', + checkIcon: 'ml-1', + content: 'flex flex-row justify-between' + }, + size: { + small: 'px-2.5 py-1.5 text-sm', + medium: 'px-4 py-2 text-base', + large: 'px-5 py-3 text-lg' } }; @@ -30,15 +60,20 @@ export const selectMenuTheme: SelectMenuTheme = { baseTheme.base, 'bg-panel text-panel-content border-panel-accent border-t-transparent' ].join(' '), - groupTitle: [baseTheme.groupTitle, 'text-panel-secondary-content'].join(' '), + groupItem: { + ...baseTheme.groupItem, + title: [baseTheme.groupItem.title, 'text-surface-content'].join(' ') + }, option: { ...baseTheme.option, - base: [baseTheme.option.base, 'text-surface-content'].join(' '), - hover: [baseTheme.option.hover, 'hover:bg-panel-accent'].join(' '), - active: [baseTheme.option.active, 'bg-panel-accent'].join(' '), + base: [baseTheme.option.base, 'text-panel-secondary-content '].join(' '), + hover: [baseTheme.option.hover, 'hover:bg-vulcan hover:text-mystic'].join( + ' ' + ), + active: [baseTheme.option.active, 'bg-vulcan hover:text-mystic'].join(' '), selected: [ baseTheme.option.selected, - 'bg-primary-active hover:bg-primary-hover light:bg-primary-active light:[&>div>span]:invert' + 'text-primary-active light:bg-primary-active light:[&>div>span]:invert' ].join(' ') } }; diff --git a/src/form/Select/SingleSelect.story.tsx b/src/form/Select/SingleSelect.story.tsx index 6af5e804..d1b37ac3 100644 --- a/src/form/Select/SingleSelect.story.tsx +++ b/src/form/Select/SingleSelect.story.tsx @@ -3,6 +3,8 @@ import { Select } from './Select'; import { SelectOption } from './SelectOption'; import { SelectMenu } from './SelectMenu'; import { SelectInput, SelectInputChip } from './SelectInput'; +import { Stack } from '@/layout'; +import { Label } from '@/layout/Block/Block.story'; export default { title: 'Components/Form/Select/Single', @@ -43,6 +45,66 @@ export const Basic = () => { ); }; +export const Sizes = () => { + const [value, setValue] = useState(null); + return ( +
+ + + + + + + + + + + + + + +
+ ); +}; + export const Fonts = () => { const [value, setValue] = useState(null); return ( diff --git a/src/form/Select/icons/CheckIcon.tsx b/src/form/Select/icons/CheckIcon.tsx new file mode 100644 index 00000000..5a64f0dd --- /dev/null +++ b/src/form/Select/icons/CheckIcon.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; + +export type CheckIconProps = { + className?: string; +}; + +export const CheckIcon: FC = ({ className }) => ( + + + + + +); diff --git a/src/form/Select/icons/CloseIcon.tsx b/src/form/Select/icons/CloseIcon.tsx index 8a787099..272a6dd8 100644 --- a/src/form/Select/icons/CloseIcon.tsx +++ b/src/form/Select/icons/CloseIcon.tsx @@ -5,16 +5,19 @@ export type CloseIconProps = { width?: number; }; -export const CloseIcon: FC = ({ height = 32, width = 32 }) => ( +export const CloseIcon: FC = ({ height = 16, width = 16 }) => ( - + ); diff --git a/src/form/Select/icons/DownArrowIcon.tsx b/src/form/Select/icons/DownArrowIcon.tsx index 24db6771..e510b076 100644 --- a/src/form/Select/icons/DownArrowIcon.tsx +++ b/src/form/Select/icons/DownArrowIcon.tsx @@ -5,10 +5,13 @@ export const DownArrowIcon: FC = () => ( xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" - width="50" - height="50" - viewBox="0 0 32 32" + width="16" + height="16" + viewBox="0 0 16 16" > - + ); diff --git a/src/form/Select/icons/RefreshIcon.tsx b/src/form/Select/icons/RefreshIcon.tsx index 2c51d8ef..fdf1b254 100644 --- a/src/form/Select/icons/RefreshIcon.tsx +++ b/src/form/Select/icons/RefreshIcon.tsx @@ -7,6 +7,9 @@ export const RefreshIcon: FC = () => ( width="64px" height="64px" > - + ); diff --git a/src/layout/List/ListItem/ListItem.tsx b/src/layout/List/ListItem/ListItem.tsx index addbc8fb..45797a51 100644 --- a/src/layout/List/ListItem/ListItem.tsx +++ b/src/layout/List/ListItem/ListItem.tsx @@ -1,7 +1,6 @@ import React, { FC, InputHTMLAttributes, LegacyRef, forwardRef } from 'react'; -import { twMerge } from 'tailwind-merge'; import { ListTheme } from '@/layout/List/ListTheme'; -import { useComponentTheme } from '@/utils'; +import { cn, useComponentTheme } from '@/utils'; export interface ListItemProps extends InputHTMLAttributes { /** @@ -29,6 +28,11 @@ export interface ListItemProps extends InputHTMLAttributes { */ dense?: boolean; + /** + * Class name for the content element. + */ + contentClassName?: string; + /** * A start component for the list item. */ @@ -59,6 +63,7 @@ export const ListItem: FC = forwardRef< ( { className, + contentClassName, children, active, disabled, @@ -82,7 +87,7 @@ export const ListItem: FC = forwardRef< role={onClick ? 'button' : 'listitem'} tabIndex={onClick ? 0 : undefined} onClick={e => !disabled && onClick?.(e)} - className={twMerge( + className={cn( theme.listItem.base, dense && theme.listItem.dense.base, disabled && theme.listItem.disabled, @@ -105,19 +110,22 @@ export const ListItem: FC = forwardRef< )}
{children}
{end && (
{end} diff --git a/src/layout/List/ListTheme.ts b/src/layout/List/ListTheme.ts index 5f279d50..4fba6355 100644 --- a/src/layout/List/ListTheme.ts +++ b/src/layout/List/ListTheme.ts @@ -47,7 +47,7 @@ const baseTheme: ListTheme = { end: 'pl-1', svg: 'fill-current' }, - content: 'text-sm overflow-wrap break-word word-wrap break-all flex-1' + content: 'overflow-wrap break-word word-wrap break-all flex-1' } }; diff --git a/tailwind.config.ts b/tailwind.config.ts index 41817b40..d011cf62 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -40,7 +40,9 @@ const config: Config = { 'button-gradient-hover': 'linear-gradient(283deg, #44F 0%, rgba(23, 23, 255, 0.10) 100%)', 'button-gradient-focus': - 'linear-gradient(283deg, #0D0DD2 0%, rgba(23, 23, 255, 0.10) 100%)' + 'linear-gradient(283deg, #0D0DD2 0%, rgba(23, 23, 255, 0.10) 100%)', + 'input-hover': 'radial-gradient(circle, #105EFF 0%, #105EFF 36%, #242433 100%)', + 'input-focus': 'radial-gradient(circle, #93B6FF 0%, #105EFF 36%, #3D3D4D 90%, #242433 100%)' } } }, From 73127e551b3b0e168bd90a14667a99b33a4b54ff Mon Sep 17 00:00:00 2001 From: SerhiiTsybulskyi Date: Mon, 27 May 2024 15:16:05 +0300 Subject: [PATCH 2/6] fix focus gradient --- src/form/Select/SelectInput/SelectInputTheme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/form/Select/SelectInput/SelectInputTheme.ts b/src/form/Select/SelectInput/SelectInputTheme.ts index 19d49d1d..0528d105 100644 --- a/src/form/Select/SelectInput/SelectInputTheme.ts +++ b/src/form/Select/SelectInput/SelectInputTheme.ts @@ -95,7 +95,7 @@ export const selectInputTheme: SelectInputTheme = { 'bg-panel text-panel-content border-panel-accent border-solid hover:border-panel-accent', // 'hover:after:bg-input-hover focus-within:after:bg-input-focus', 'hover:after:bg-[radial-gradient(circle,_#105EFF_0%,_#105EFF_36%,_#242433_100%)]', - 'focus-within:after:bg-[radial-gradient(circle,_#93B6FF_0%,_#105EFF_36%,_#3D3D4D 90%,_#242433_100%)]', + 'focus-within:after:bg-[radial-gradient(circle,_#93B6FF_0%,_#105EFF_36%,_#3D3D4D_90%,_#242433_100%)]', 'hover:after:content-[""] hover:after:absolute hover:after:mx-1 hover:after:h-px after:z-[2] hover:after:rounded hover:after:-bottom-[0px] hover:after:inset-x-0.5', 'focus-within:after:content-[""] focus-within:after:absolute focus-within:after:mx-0 focus-within:after:h-px after:z-[2] focus-within:after:rounded focus-within:after:-bottom-[0px] focus-within:after:inset-x-0.5' ].join(' '), From 05bb5f344917aaff2c72a73b9fe7f4a4dc0b833a Mon Sep 17 00:00:00 2001 From: SerhiiTsybulskyi Date: Mon, 27 May 2024 16:16:43 +0300 Subject: [PATCH 3/6] add light theme --- .storybook/preview-head.html | 2 +- src/form/Range/Range.story.tsx | 9 +-------- src/form/Select/SelectInput/SelectInputTheme.ts | 11 +++++------ src/form/Select/SelectMenu/SelectMenuTheme.ts | 17 +++++++++-------- tailwind.config.ts | 4 +--- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 23e2b31f..5a676587 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -39,7 +39,7 @@ .light .sb-show-main.sb-main-centered { color: #11111F; - background: #E6E6F0; + background: #F7F7FA; } .dark .sb-show-main.sb-main-centered { diff --git a/src/form/Range/Range.story.tsx b/src/form/Range/Range.story.tsx index 175e26e8..2a2a47b0 100644 --- a/src/form/Range/Range.story.tsx +++ b/src/form/Range/Range.story.tsx @@ -4,14 +4,7 @@ import { RangeSingle } from './RangeSingle'; export default { title: 'Components/Form/Range', - component: RangeSingle, - decorators: [ - Story => ( -
- -
- ) - ] + component: RangeSingle }; export const Single = () => { diff --git a/src/form/Select/SelectInput/SelectInputTheme.ts b/src/form/Select/SelectInput/SelectInputTheme.ts index 0528d105..dc477f7f 100644 --- a/src/form/Select/SelectInput/SelectInputTheme.ts +++ b/src/form/Select/SelectInput/SelectInputTheme.ts @@ -92,10 +92,9 @@ export const selectInputTheme: SelectInputTheme = { ...baseTheme, base: [ baseTheme.base, - 'bg-panel text-panel-content border-panel-accent border-solid hover:border-panel-accent', - // 'hover:after:bg-input-hover focus-within:after:bg-input-focus', - 'hover:after:bg-[radial-gradient(circle,_#105EFF_0%,_#105EFF_36%,_#242433_100%)]', - 'focus-within:after:bg-[radial-gradient(circle,_#93B6FF_0%,_#105EFF_36%,_#3D3D4D_90%,_#242433_100%)]', + 'bg-panel text-panel-content border-panel-accent border-solid hover:border-panel-accent light:hover:border-panel-accent', + 'hover:after:bg-[radial-gradient(circle,_#105EFF_0%,_#105EFF_36%,_#242433_100%)] light:hover:after:bg-[radial-gradient(circle,_#105EFF_0%,_#105EFF_36%,_#E6E6F0_100%)]', + 'focus-within:after:bg-[radial-gradient(circle,_#93B6FF_0%,_#105EFF_36%,_#3D3D4D_90%,_#242433_100%)] light:focus-within:after:bg-[radial-gradient(circle,_#105EFF_10%,_#93B6FF_36%,_#E6E6F0_90%)]', 'hover:after:content-[""] hover:after:absolute hover:after:mx-1 hover:after:h-px after:z-[2] hover:after:rounded hover:after:-bottom-[0px] hover:after:inset-x-0.5', 'focus-within:after:content-[""] focus-within:after:absolute focus-within:after:mx-0 focus-within:after:h-px after:z-[2] focus-within:after:rounded focus-within:after:-bottom-[0px] focus-within:after:inset-x-0.5' ].join(' '), @@ -105,9 +104,9 @@ export const selectInputTheme: SelectInputTheme = { ].join(' '), disabled: [ baseTheme.disabled, - 'text-panel-secondary-content/40 border-surface' + 'text-panel-secondary-content/40 border-surface light:hover:border-surface' ].join(' '), - error: [baseTheme.error, 'border-error'].join(' '), + error: [baseTheme.error, 'border-error light:border-error/20'].join(' '), suffix: { ...baseTheme.suffix, button: [baseTheme.suffix.button, 'hover:cursor-pointer'].join(' ') diff --git a/src/form/Select/SelectMenu/SelectMenuTheme.ts b/src/form/Select/SelectMenu/SelectMenuTheme.ts index 8631605c..91f21bdc 100644 --- a/src/form/Select/SelectMenu/SelectMenuTheme.ts +++ b/src/form/Select/SelectMenu/SelectMenuTheme.ts @@ -67,14 +67,12 @@ export const selectMenuTheme: SelectMenuTheme = { option: { ...baseTheme.option, base: [baseTheme.option.base, 'text-panel-secondary-content '].join(' '), - hover: [baseTheme.option.hover, 'hover:bg-vulcan hover:text-mystic'].join( - ' ' - ), + hover: [ + baseTheme.option.hover, + 'hover:bg-vulcan hover:text-mystic light:hover:bg-vulcan/5 light:hover:text-panel-secondary-content' + ].join(' '), active: [baseTheme.option.active, 'bg-vulcan hover:text-mystic'].join(' '), - selected: [ - baseTheme.option.selected, - 'text-primary-active light:bg-primary-active light:[&>div>span]:invert' - ].join(' ') + selected: [baseTheme.option.selected, 'text-primary-active'].join(' ') } }; @@ -84,7 +82,10 @@ export const cssVarsSelectMenuTheme: SelectMenuTheme = { baseTheme.base, 'bg-[var(--select-menu-background)] [border:_var(--select-menu-border)] rounded-[var(--select-menu-border-radius)]' ].join(' '), - groupTitle: [baseTheme.groupTitle, 'text-gray-600'].join(' '), + groupItem: { + ...baseTheme.groupItem, + title: [baseTheme.groupItem.title, 'text-gray-600'].join(' ') + }, option: { ...baseTheme.option, base: [ diff --git a/tailwind.config.ts b/tailwind.config.ts index d011cf62..41817b40 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -40,9 +40,7 @@ const config: Config = { 'button-gradient-hover': 'linear-gradient(283deg, #44F 0%, rgba(23, 23, 255, 0.10) 100%)', 'button-gradient-focus': - 'linear-gradient(283deg, #0D0DD2 0%, rgba(23, 23, 255, 0.10) 100%)', - 'input-hover': 'radial-gradient(circle, #105EFF 0%, #105EFF 36%, #242433 100%)', - 'input-focus': 'radial-gradient(circle, #93B6FF 0%, #105EFF 36%, #3D3D4D 90%, #242433 100%)' + 'linear-gradient(283deg, #0D0DD2 0%, rgba(23, 23, 255, 0.10) 100%)' } } }, From 89e24864394bc5ed44d812964e069da56660b42c Mon Sep 17 00:00:00 2001 From: SerhiiTsybulskyi Date: Mon, 27 May 2024 17:38:06 +0300 Subject: [PATCH 4/6] add display mode with counter --- src/form/Select/MultiSelect.story.tsx | 23 ++++++++++ src/form/Select/Select.tsx | 7 +++ src/form/Select/SelectInput/SelectInput.tsx | 43 ++++++++++++++----- .../Select/SelectInput/SelectInputTheme.ts | 4 +- 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/form/Select/MultiSelect.story.tsx b/src/form/Select/MultiSelect.story.tsx index e331e057..f8d671c4 100644 --- a/src/form/Select/MultiSelect.story.tsx +++ b/src/form/Select/MultiSelect.story.tsx @@ -45,6 +45,29 @@ export const Basic = () => { ); }; +export const ValueCount = () => { + const [value, setValue] = useState(['facebook', 'twitter']); + return ( +
+ +
+ ); +}; + export const Disabled = () => { const [value, setValue] = useState(['facebook']); return ( diff --git a/src/form/Select/Select.tsx b/src/form/Select/Select.tsx index a8c2b328..a659efed 100644 --- a/src/form/Select/Select.tsx +++ b/src/form/Select/Select.tsx @@ -84,6 +84,11 @@ export interface SelectProps { */ multiple?: boolean; + /** + * The display type of the selected values. + */ + multipleDisplayType?: 'chip' | 'count'; + /** * Default placeholder text. */ @@ -250,6 +255,7 @@ export const Select: FC = ({ defaultFilterValue, required, size = 'medium', + multipleDisplayType = 'chip', input = , menu = , onRefresh, @@ -783,6 +789,7 @@ export const Select: FC = ({ clearable={clearable} menuDisabled={menuDisabled} size={size} + multipleDisplayType={multipleDisplayType} onSelectedChange={toggleSelectedOption} onExpandClick={onInputExpanded} onKeyDown={onInputKeyedDown} diff --git a/src/form/Select/SelectInput/SelectInput.tsx b/src/form/Select/SelectInput/SelectInput.tsx index 3264a8ed..8e80dbe7 100644 --- a/src/form/Select/SelectInput/SelectInput.tsx +++ b/src/form/Select/SelectInput/SelectInput.tsx @@ -15,7 +15,6 @@ import { CloseIcon } from '@/form/Select/icons/CloseIcon'; import { DotsLoader } from '@/elements/Loader/DotsLoader'; import { RefreshIcon } from '@/form/Select/icons/RefreshIcon'; import { SelectInputChip, SelectInputChipProps } from './SelectInputChip'; -import { twMerge } from 'tailwind-merge'; import { cn, useComponentTheme } from '@/utils'; import { SelectTheme } from '@/form/Select/SelectTheme'; import { CloneElement } from '@/utils'; @@ -96,6 +95,11 @@ export interface SelectInputProps { */ multiple?: boolean; + /** + * The display type of the selected values. + */ + multipleDisplayType?: 'chip' | 'count'; + /** * If true, the select input is loading. */ @@ -256,6 +260,7 @@ export const SelectInput: FC = ({ menuDisabled, menuOpen, size, + multipleDisplayType, refreshIcon = , closeIcon = , expandIcon = , @@ -436,14 +441,12 @@ export const SelectInput: FC = ({ const renderPrefix = useCallback(() => { if (multiple) { const multipleOptions = selectedOption as SelectOptionProps[]; - if (multipleOptions?.length) { + if (multipleOptions?.length && multipleDisplayType === 'chip') { return (
{multipleOptions.map(option => ( @@ -465,7 +468,7 @@ export const SelectInput: FC = ({ if (singleOption?.inputLabel && !inputText) { return (
= ({ disabled, inputText, multiple, + multipleDisplayType, onSelectedChange, onTagKeyDown, selectedOption, @@ -493,6 +497,18 @@ export const SelectInput: FC = ({ theme.single ]); + const renderCount = useCallback(() => { + if (multiple) { + const multipleOptions = selectedOption as SelectOptionProps[]; + + if (multipleDisplayType === 'count') { + return ( +
{multipleOptions.length}
+ ); + } + } + }, [multiple, multipleDisplayType, selectedOption, theme.multiple]); + return (
= ({ placeholderIsMinWidth={false} />
+ {multiple && multipleDisplayType === 'count' && ( +
+ {(selectedOption as SelectOptionProps[]).length} +
+ )}
{refreshable && !loading && (