= ({
]);
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..dc477f7f 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,36 @@ 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 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(' '),
+ placeholder: [
+ baseTheme.placeholder,
+ 'placeholder:text-secondary-content'
+ ].join(' '),
+ disabled: [
+ baseTheme.disabled,
+ 'text-panel-secondary-content/40 border-surface light:hover:border-surface'
].join(' '),
- disabled: [baseTheme.disabled, 'opacity-75'].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(' ')
+ },
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..91f21bdc 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,16 +60,19 @@ 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(' '),
- selected: [
- baseTheme.option.selected,
- 'bg-primary-active hover:bg-primary-hover light:bg-primary-active light:[&>div>span]:invert'
- ].join(' ')
+ base: [baseTheme.option.base, 'text-panel-secondary-content '].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'].join(' ')
}
};
@@ -49,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/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..25b0d725 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,
@@ -95,29 +100,32 @@ export const ListItem: FC = forwardRef<
>
{start && (
{start}
)}
{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'
}
};