Skip to content
Merged
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
130 changes: 130 additions & 0 deletions packages/ui-components/src/Common/Select/StatelessSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { ChevronDownIcon } from '@heroicons/react/24/solid';
import classNames from 'classnames';
import { useId, useMemo } from 'react';

import type { SelectGroup, SelectProps } from '#ui/Common/Select';
import { isStringArray, isValuesArray } from '#ui/Common/Select';
Comment thread
canerakdas marked this conversation as resolved.
Outdated

import styles from '../index.module.css';

export type StatelessSelectProps<T extends string> = Omit<
SelectProps<T>,
'onChange' | 'loading'
> & {
targetElement: string;
Comment thread
canerakdas marked this conversation as resolved.
Outdated
};

const StatelessSelect = <T extends string>({
values = [],
defaultValue,
placeholder,
label,
inline,
className,
ariaLabel,
disabled = false,
as: Component = 'div',
targetElement,
}: StatelessSelectProps<T>) => {
Comment thread
canerakdas marked this conversation as resolved.
const id = useId();

const mappedValues = useMemo(() => {
let mappedValues = values;

if (isStringArray(mappedValues)) {
mappedValues = mappedValues.map(value => ({
label: value,
value: value,
}));
}

if (isValuesArray(mappedValues)) {
return [{ items: mappedValues }];
}

return mappedValues as Array<SelectGroup<T>>;
}, [values]);

// Find the current/default item to display in summary
const currentItem = useMemo(
() =>
mappedValues
.flatMap(({ items }) => items)
.find(item => item.value === defaultValue),
[mappedValues, defaultValue]
);

return (
<noscript>
Comment thread
canerakdas marked this conversation as resolved.
Outdated
<style>{`.${targetElement} { display: none!important; }`}</style>
<div
className={classNames(
styles.select,
styles.noscript,
{ [styles.inline]: inline },
className
)}
>
{label && (
<label className={styles.label} htmlFor={id}>
{label}
</label>
)}

<details className={styles.trigger} id={id}>
<summary
className={styles.summary}
aria-label={ariaLabel}
aria-disabled={disabled}
>
{currentItem ? (
<span className={styles.selectedValue}>
{currentItem.iconImage}
<span>{currentItem.label}</span>
</span>
) : (
<span className={styles.placeholder}>{placeholder}</span>
)}
<ChevronDownIcon className={styles.icon} />
</summary>

<div
className={classNames(styles.dropdown, { [styles.inline]: inline })}
>
{mappedValues.map(({ label: groupLabel, items }, groupKey) => (
<div
key={groupLabel?.toString() ?? groupKey}
className={styles.group}
>
{groupLabel && (
<div className={classNames(styles.item, styles.label)}>
{groupLabel}
</div>
)}

{items.map(
({ value, label, iconImage, disabled: itemDisabled }) => (
<Component
key={value}
href={value}
className={classNames(styles.item, styles.text, {
[styles.disabled]: itemDisabled || disabled,
[styles.selected]: value === defaultValue,
})}
aria-disabled={itemDisabled || disabled}
>
{iconImage}
<span>{label}</span>
</Component>
)
)}
</div>
))}
</div>
</details>
</div>
</noscript>
);
};

export default StatelessSelect;
37 changes: 37 additions & 0 deletions packages/ui-components/src/Common/Select/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,40 @@
text-neutral-700
dark:text-neutral-200;
}

.noscript {
summary {
@apply flex
w-full
justify-between;
}

.dropdown {
@apply absolute
left-4
mt-6;
}

.text {
@apply hover:outline-hidden
block
whitespace-normal
pl-4
text-neutral-800
hover:bg-green-500
hover:text-white
dark:text-neutral-200
dark:hover:bg-green-600
dark:hover:text-white;

span {
@apply h-auto;
}
}

.inline {
Comment thread
canerakdas marked this conversation as resolved.
.text {
@apply pl-2.5;
}
}
}
29 changes: 24 additions & 5 deletions packages/ui-components/src/Common/Select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import classNames from 'classnames';
import { useEffect, useId, useMemo, useState } from 'react';
import type { ReactElement, ReactNode } from 'react';

import StatelessSelect from '#ui/Common/Select/StatelessSelect';
import Skeleton from '#ui/Common/Skeleton';
import type { FormattedMessage } from '#ui/types';
import type { FormattedMessage, LinkLike } from '#ui/types';

import styles from './index.module.css';

Expand All @@ -23,15 +24,17 @@ export type SelectGroup<T extends string> = {
items: Array<SelectValue<T>>;
};

const isStringArray = (values: Array<unknown>): values is Array<string> =>
export const isStringArray = (
values: Array<unknown>
): values is Array<string> =>
Boolean(values[0] && typeof values[0] === 'string');

const isValuesArray = <T extends string>(
export const isValuesArray = <T extends string>(
values: Array<unknown>
): values is Array<SelectValue<T>> =>
Boolean(values[0] && typeof values[0] === 'object' && 'value' in values[0]);

type SelectProps<T extends string> = {
export type SelectProps<T extends string> = {
values: Array<SelectGroup<T>> | Array<T> | Array<SelectValue<T>>;
defaultValue?: T;
placeholder?: string;
Expand All @@ -42,6 +45,7 @@ type SelectProps<T extends string> = {
ariaLabel?: string;
loading?: boolean;
disabled?: boolean;
as?: LinkLike | 'div';
};

const Select = <T extends string>({
Expand All @@ -55,8 +59,10 @@ const Select = <T extends string>({
ariaLabel,
loading = false,
disabled = false,
as,
}: SelectProps<T>): ReactNode => {
const id = useId();
const uniqueSelectClass = `select-${id.replace(/[^a-zA-Z0-9]/g, '')}`;
const [value, setValue] = useState(defaultValue);

useEffect(() => setValue(defaultValue), [defaultValue]);
Expand Down Expand Up @@ -133,7 +139,8 @@ const Select = <T extends string>({
className={classNames(
styles.select,
{ [styles.inline]: inline },
className
className,
uniqueSelectClass
)}
>
{label && (
Expand Down Expand Up @@ -183,6 +190,18 @@ const Select = <T extends string>({
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
</span>
<StatelessSelect
values={values}
defaultValue={defaultValue}
placeholder={placeholder}
label={label}
inline={inline}
className={className}
ariaLabel={ariaLabel}
disabled={disabled}
targetElement={uniqueSelectClass}
as={as}
/>
</Skeleton>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/ui-components/src/Containers/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const SideBar: FC<PropsWithChildren<SidebarProps>> = ({
placeholder={placeholder}
onChange={onSelect}
className={styles.mobileSelect}
as={as}
/>
)}

Expand Down
Loading