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 1e0093c887 - Show all commits

View File

@ -1,63 +1,70 @@
import type { HTMLAttributes } from 'react'; import type { HTMLAttributes } from "react";
import clsx from "clsx";
import { RemoveIcon } from "../Icon/Icon";
export interface ListCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> { export interface ListCardProps
title: string; extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
subtitle?: string; title: string;
onRemove?: () => void; subtitle?: string;
onRemove?: () => void;
} }
const baseClasses = [ const baseClasses = clsx(
'px-(--padding-md) py-(--padding-md)', "px-(--padding-md) py-(--padding-md)",
'bg-sky-35 border border-sky-100 rounded-(--border-radius-md)', "bg-sky-35 border border-sky-100 rounded-(--border-radius-md)",
'flex items-center justify-between', "flex items-center justify-between",
'group text-base-ink-strong hover:bg-sky-70 hover:text-base-ink-max focus-visible:text-base-ink-max', "group cursor-pointer",
'focus-visible:border-primary focus-visible:border-[length:var(--border-width-sm)] focus-visible:outline focus-visible:outline-sky-100 focus-visible:outline-[length:var(--border-width-lg)]', );
].join(' ');
const removeIconClasses = 'w-(--font-size-body-md) h-(--font-size-body-md)'; const stateClasses = clsx(
"text-base-ink-strong",
"hover:bg-sky-70 hover:text-base-ink-max",
"focus-visible:text-base-ink-max",
"focus-visible:border-primary focus-visible:border-[length:var(--border-width-sm)]",
"focus-visible:outline focus-visible:outline-sky-100 focus-visible:outline-[length:var(--border-width-lg)]",
);
function RemoveIcon() { const removeButtonClasses = clsx(
return ( "shrink-0 ml-(--spacing-sm) cursor-pointer",
<svg "text-base-ink-placeholder",
className={removeIconClasses} "group-hover:text-base-ink-max group-focus-visible:text-base-ink-max",
viewBox="0 0 24 24" "focus-visible:outline-none",
fill="none" );
stroke="currentColor"
strokeWidth="2" export default function ListCard({
strokeLinecap="round" title,
strokeLinejoin="round" subtitle,
onRemove,
className,
...props
}: ListCardProps) {
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && onRemove) {
onRemove();
}
};
return (
<div
className={clsx(baseClasses, stateClasses, className)}
tabIndex={0}
onKeyDown={handleKeyDown}
{...props}
>
<div>
<div className="body-light-sm">{title}</div>
{subtitle && <div className="body-light-sm">{subtitle}</div>}
</div>
{onRemove && (
<button
type="button"
tabIndex={-1}
onClick={onRemove}
className={removeButtonClasses}
> >
<line x1="18" y1="6" x2="6" y2="18" /> <RemoveIcon />
<line x1="6" y1="6" x2="18" y2="18" /> </button>
</svg> )}
); </div>
} );
export default function ListCard({ title, subtitle = '', onRemove, className = '', ...props }: ListCardProps) {
const classes = [baseClasses, className].filter(Boolean).join(' ');
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && onRemove) {
onRemove();
}
};
return (
<div className={classes} {...props} tabIndex={0} onKeyDown={handleKeyDown}>
<div>
<div className="body-light-sm">{title}</div>
{subtitle && <div className="body-light-sm">{subtitle}</div>}
</div>
{onRemove && (
<button
type="button"
tabIndex={-1}
onClick={onRemove}
className="shrink-0 ml-(--spacing-sm) text-base-ink-placeholder group-hover:text-base-ink-max group-focus-visible:text-base-ink-max focus-visible:outline-none cursor-pointer"
>
<RemoveIcon />
</button>
)}
</div>
);
} }