List components #31

Merged
stne3960 merged 68 commits from list_item into main 2025-12-18 12:41:13 +01:00
Showing only changes of commit 99a6d9bde5 - Show all commits

View File

@ -1,77 +1,90 @@
import Combobox, { type ComboboxOption, type ComboboxSize } from '../Combobox/Combobox'; import type { HTMLAttributes } from "react";
import ListCard from '../ListCard/ListCard'; import clsx from "clsx";
import Combobox, {
type ComboboxOption,
type ComboboxSize,
} from "../Combobox/Combobox";
import ListCard from "../ListCard/ListCard";
export interface ParticipantPickerProps { export interface ParticipantPickerProps
options: ComboboxOption[]; extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
value: string[]; options: ComboboxOption[];
onChange: (value: string[]) => void; value: string[];
placeholder?: string; onChange: (value: string[]) => void;
label?: string; placeholder?: string;
noResultsText?: string; label: string;
size?: ComboboxSize; hideLabel?: boolean;
fullWidth?: boolean; noResultsText?: string;
customWidth?: string; size?: ComboboxSize;
/** Called when search term changes. When provided, local filtering is disabled (assumes API filtering). */ fullWidth?: boolean;
onSearchChange?: (term: string) => void; customWidth?: string;
/** Called when search term changes. When provided, local filtering is disabled (assumes API filtering). */
onSearchChange?: (term: string) => void;
} }
const widthClasses: Record<ComboboxSize, string> = { const widthClasses: Record<ComboboxSize, string> = {
sm: 'w-(--text-input-default-width-md)', sm: "w-(--text-input-default-width-md)",
md: 'w-(--text-input-default-width-md)', md: "w-(--text-input-default-width-md)",
lg: 'w-(--text-input-default-width-lg)', lg: "w-(--text-input-default-width-lg)",
}; };
export default function ParticipantPicker({ export default function ParticipantPicker({
options, options,
value, value,
onChange, onChange,
placeholder = 'Sök...', placeholder = "Sök...",
label, label,
noResultsText = 'Inga resultat', hideLabel = false,
size = 'md', noResultsText = "Inga resultat",
fullWidth = false, size = "md",
customWidth, fullWidth = false,
onSearchChange, customWidth,
onSearchChange,
className,
style,
...props
}: ParticipantPickerProps) { }: ParticipantPickerProps) {
const handleRemove = (valueToRemove: string) => { const handleRemove = (valueToRemove: string) => {
onChange(value.filter((v) => v !== valueToRemove)); onChange(value.filter((v) => v !== valueToRemove));
}; };
const selectedOptions = options.filter((opt) => value.includes(opt.value)); const selectedOptions = options.filter((opt) => value.includes(opt.value));
const containerClasses = fullWidth const containerClasses = clsx(
? 'flex flex-col gap-(--spacing-sm) w-full' "flex flex-col gap-(--spacing-sm)",
: customWidth fullWidth && "w-full",
? 'flex flex-col gap-(--spacing-sm)' !fullWidth && !customWidth && widthClasses[size],
: `flex flex-col gap-(--spacing-sm) ${widthClasses[size]}`; className,
const widthStyle = customWidth ? { width: customWidth } : undefined; );
const widthStyle = customWidth ? { width: customWidth, ...style } : style;
return ( return (
<div className={containerClasses} style={widthStyle}> <div className={containerClasses} style={widthStyle} {...props}>
<Combobox <Combobox
options={options} options={options}
value={value} value={value}
onChange={(v) => onChange(v as string[])} onChange={(v) => onChange(v as string[])}
placeholder={placeholder} placeholder={placeholder}
label={label} label={label}
noResultsText={noResultsText} hideLabel={hideLabel}
size={size} noResultsText={noResultsText}
fullWidth size={size}
multiple fullWidth
onSearchChange={onSearchChange} multiple
onSearchChange={onSearchChange}
/>
{selectedOptions.length > 0 && (
<div className="flex flex-col gap-(--spacing-sm)">
{selectedOptions.map((option) => (
<ListCard
key={option.value}
title={option.label}
subtitle={option.subtitle}
onRemove={() => handleRemove(option.value)}
/> />
{selectedOptions.length > 0 && ( ))}
<div className="flex flex-col gap-(--spacing-sm)">
{selectedOptions.map((option) => (
<ListCard
key={option.value}
title={option.label}
subtitle={option.subtitle}
onRemove={() => handleRemove(option.value)}
/>
))}
</div>
)}
</div> </div>
); )}
</div>
);
} }