From 65d978e4323f14def82b7572c58527759770f970 Mon Sep 17 00:00:00 2001 From: Inge Fossland Date: Wed, 18 Dec 2024 10:22:36 +0100 Subject: [PATCH] fix: split ListItemHeader into ListItemLink + rest (#137) fix: adjust ListItem sizes (sm+xs) fix: list => defaultItemSize/Color --- .../AccessAreaList/AccessAreaListItem.tsx | 2 +- lib/components/Dialog/DialogBorder.tsx | 20 ------ lib/components/Dialog/DialogByline.tsx | 2 +- lib/components/Dialog/DialogHeading.tsx | 12 ++-- lib/components/Dialog/DialogLabel.tsx | 8 +-- lib/components/Dialog/DialogListItem.tsx | 64 +++++++---------- lib/components/Dialog/dialogBorder.module.css | 16 ----- .../Dialog/dialogHeading.module.css | 4 +- .../Dialog/dialogListItem.module.css | 39 +++++++--- lib/components/Dialog/index.ts | 1 - lib/components/List/List.stories.tsx | 7 +- lib/components/List/List.tsx | 16 +++-- lib/components/List/ListItem.stories.tsx | 71 +++++++++++++------ lib/components/List/ListItem.tsx | 2 + lib/components/List/ListItemBase.tsx | 4 +- .../List/ListItemHeader.stories.tsx | 51 ++++++------- lib/components/List/ListItemHeader.tsx | 46 ++++-------- lib/components/List/ListItemLink.tsx | 52 ++++++++++++++ lib/components/List/ListItemMedia.tsx | 27 +++---- lib/components/List/index.ts | 1 + lib/components/List/listItemBase.module.css | 27 +++---- lib/components/List/listItemHeader.module.css | 41 +++-------- lib/components/List/listItemLabel.module.css | 32 +++------ lib/components/List/listItemLink.module.css | 23 ++++++ lib/components/List/listItemMedia.module.css | 18 ++--- lib/components/List/listItemSelect.module.css | 7 -- .../ResourceList/ResourceList.stories.tsx | 4 +- lib/components/ResourceList/ResourceList.tsx | 4 +- .../ResourceList/ResourceListItem.stories.tsx | 1 - .../Searchbar/Searchbar.stories.tsx | 22 +++--- package.json | 10 +-- 31 files changed, 309 insertions(+), 325 deletions(-) delete mode 100644 lib/components/Dialog/DialogBorder.tsx delete mode 100644 lib/components/Dialog/dialogBorder.module.css create mode 100644 lib/components/List/ListItemLink.tsx create mode 100644 lib/components/List/listItemLink.module.css diff --git a/lib/components/AccessAreaList/AccessAreaListItem.tsx b/lib/components/AccessAreaList/AccessAreaListItem.tsx index 7ff4c299..67e706e5 100644 --- a/lib/components/AccessAreaList/AccessAreaListItem.tsx +++ b/lib/components/AccessAreaList/AccessAreaListItem.tsx @@ -19,7 +19,7 @@ export const AccessAreaListItem = ({ ...props }: AccessAreaListItemProps) => { return ( - + { - return ( -
- {children} -
- ); -}; diff --git a/lib/components/Dialog/DialogByline.tsx b/lib/components/Dialog/DialogByline.tsx index 1be07aad..199ac4d8 100644 --- a/lib/components/Dialog/DialogByline.tsx +++ b/lib/components/Dialog/DialogByline.tsx @@ -1,7 +1,7 @@ import { type AvatarProps, Byline, type BylineSize } from '..'; export interface DialogBylineProps { - sender: AvatarProps; + sender?: AvatarProps; recipient?: AvatarProps; recipientLabel?: string; recipientGroup?: boolean; diff --git a/lib/components/Dialog/DialogHeading.tsx b/lib/components/Dialog/DialogHeading.tsx index 840eee60..e1059d70 100644 --- a/lib/components/Dialog/DialogHeading.tsx +++ b/lib/components/Dialog/DialogHeading.tsx @@ -1,13 +1,13 @@ import type { ReactNode } from 'react'; -import { DialogLabel, type DialogListItemSize, type DialogListItemVariant, Skeleton } from '..'; +import { DialogLabel, type DialogListItemSize, type DialogListItemState, Skeleton } from '..'; import styles from './dialogHeading.module.css'; export type DialogHeadingProps = { loading?: boolean; /** Size */ size?: DialogListItemSize; - /** Variant */ - variant?: DialogListItemVariant; + /** Type */ + state?: DialogListItemState; /** Label */ label?: string; /** Variant */ @@ -19,14 +19,14 @@ export type DialogHeadingProps = { /** * Dialog heading */ -export const DialogHeading = ({ loading, size = 'sm', seen = false, variant, label, children }: DialogHeadingProps) => { +export const DialogHeading = ({ loading, size = 'sm', seen = false, state, label, children }: DialogHeadingProps) => { return (
-

+

{children}

{!loading && label && ( - + {label} )} diff --git a/lib/components/Dialog/DialogLabel.tsx b/lib/components/Dialog/DialogLabel.tsx index 246c663c..89913888 100644 --- a/lib/components/Dialog/DialogLabel.tsx +++ b/lib/components/Dialog/DialogLabel.tsx @@ -1,11 +1,11 @@ import type { ReactNode } from 'react'; import { MetaItem, type MetaItemSize } from '../Meta'; -export type DialogLabelVariant = 'normal' | 'trashed' | 'archived'; +export type DialogLabelType = 'normal' | 'trashed' | 'archived'; export interface DialogLabelProps { size?: MetaItemSize; - variant?: DialogLabelVariant; + type?: DialogLabelType; label?: string; children?: ReactNode; } @@ -14,8 +14,8 @@ export interface DialogLabelProps { * Dialog label. */ -export const DialogLabel = ({ size = 'xs', variant, label, children }: DialogLabelProps) => { - switch (variant) { +export const DialogLabel = ({ size = 'xs', type, label, children }: DialogLabelProps) => { + switch (type) { case 'trashed': return ( diff --git a/lib/components/Dialog/DialogListItem.tsx b/lib/components/Dialog/DialogListItem.tsx index 24d08b69..99ed289f 100644 --- a/lib/components/Dialog/DialogListItem.tsx +++ b/lib/components/Dialog/DialogListItem.tsx @@ -1,7 +1,5 @@ -import type { ElementType } from 'react'; import { type AvatarProps, - DialogBorder, DialogByline, DialogHeading, DialogMetadata, @@ -11,42 +9,35 @@ import { type DialogStatusProps, type DialogTouchedByActor, ListItemBase, - type ListItemColor, - ListItemHeader, + type ListItemBaseProps, ListItemLabel, + ListItemLink, + type ListItemLinkProps, Skeleton, } from '..'; export type DialogListItemSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; -export type DialogListItemVariant = 'normal' | 'trashed' | 'archived'; +export type DialogListItemState = 'normal' | 'trashed' | 'archived'; import styles from './dialogListItem.module.css'; -export interface DialogListItemProps { +export interface DialogListItemProps extends ListItemBaseProps, ListItemLinkProps { /** Dialog title */ title: string; /** Dialog sender */ - sender: AvatarProps; + sender?: AvatarProps; /** Dialog description */ description?: string; /** Dialog summary (will override description) */ summary?: string; - /** Dialog is loading */ - loading?: boolean; - /** Render as */ - as?: ElementType; - /** Size */ + /** Dialog size */ size?: DialogListItemSize; - /** Variant */ - variant?: DialogListItemVariant; - /** Link */ - href?: string; - /** OnClick handler */ - onClick?: () => void; /** Select: Use to support batch operations */ select?: DialogSelectProps; - /** Dialog is selected */ + /** Selected: Use to support batch operations */ selected?: boolean; + /** Dialog state */ + state?: DialogListItemState; /** Dialog status */ status?: DialogStatusProps; /** Dialog Recipient */ @@ -75,8 +66,6 @@ export interface DialogListItemProps { tabIndex?: number; /** Custom label */ label?: string; - /** Custom color */ - color?: ListItemColor; /** Dialog has been seen */ seen?: boolean; /** Dialog is seen by the user */ @@ -97,7 +86,7 @@ export interface DialogListItemProps { export const DialogListItem = ({ size = 'xl', - variant = 'normal', + state = 'normal', loading, select, status, @@ -123,33 +112,27 @@ export const DialogListItem = ({ summary, ...rest }: DialogListItemProps) => { - const applicableVariant = trashedAt ? 'trashed' : archivedAt ? 'archived' : variant; + const applicableState = trashedAt ? 'trashed' : archivedAt ? 'archived' : state; if (size === 'xs' || size === 'sm' || size === 'md') { return ( - - + +
- - +
+
); } return ( - - } - > - + + +
- + {title} - - +
+
+ {select && }
); }; diff --git a/lib/components/Dialog/dialogBorder.module.css b/lib/components/Dialog/dialogBorder.module.css deleted file mode 100644 index 9b4a8698..00000000 --- a/lib/components/Dialog/dialogBorder.module.css +++ /dev/null @@ -1,16 +0,0 @@ -.border { - position: relative; - border-left: 0.25rem solid; - border-color: var(--theme-surface-active); - - display: flex; - flex-direction: column; -} - -.border[data-seen="true"] { - border-color: var(--neutral-surface-default); -} - -.border[data-loading="true"] { - border-color: var(--neutral-surface-default); -} diff --git a/lib/components/Dialog/dialogHeading.module.css b/lib/components/Dialog/dialogHeading.module.css index 5d73dbdc..d3bf112a 100644 --- a/lib/components/Dialog/dialogHeading.module.css +++ b/lib/components/Dialog/dialogHeading.module.css @@ -44,11 +44,11 @@ font-weight: 400; } -.title[data-variant="archived"] { +.title[data-state="archived"] { font-weight: 400; } -.title[data-variant="trashed"] { +.title[data-state="trashed"] { font-weight: 400; text-decoration: line-through; } diff --git a/lib/components/Dialog/dialogListItem.module.css b/lib/components/Dialog/dialogListItem.module.css index 196b6a42..6c32e4c5 100644 --- a/lib/components/Dialog/dialogListItem.module.css +++ b/lib/components/Dialog/dialogListItem.module.css @@ -1,15 +1,39 @@ -/*.item[data-size="md"] { - padding: 0.75em; +.link[data-size="xl"] { + padding: 1em; } -.item[data-size="lg"] { +.link[data-size="lg"] { padding: 0.875em; } -.item[data-size="xl"] { - padding: 1em; +.link[data-size="md"] { + padding: 0.75em; +} + +.link[data-size="sm"] { + padding: 0.625em; +} + +.link[data-size="sm"], +.link[data-size="xs"] { + padding: 0.625em; +} + +.border { + position: relative; + border-left: 0.25rem solid; + border-color: var(--theme-surface-active); + display: flex; + flex-direction: column; +} + +.border[data-seen="true"] { + border-color: var(--neutral-surface-default); +} + +.border[data-loading="true"] { + border-color: var(--neutral-surface-default); } -*/ .border[data-size="xs"], .border[data-size="sm"] { @@ -20,19 +44,16 @@ } .border[data-size="md"] { - margin: 0.25rem 0; padding-left: 0.75rem; row-gap: 0.5rem; } .border[data-size="lg"] { - margin: 0.25rem 0; padding-left: 0.875rem; row-gap: 0.875rem; } .border[data-size="xl"] { - margin: 0.25rem 0; padding-left: 1rem; row-gap: 1rem; } diff --git a/lib/components/Dialog/index.ts b/lib/components/Dialog/index.ts index 8b14e360..5ae15974 100644 --- a/lib/components/Dialog/index.ts +++ b/lib/components/Dialog/index.ts @@ -8,7 +8,6 @@ export * from './DialogByline'; export * from './DialogActions'; export * from './DialogActivityLog'; -export * from './DialogBorder'; export * from './DialogContent'; export * from './DialogSection'; export * from './DialogSeenBy'; diff --git a/lib/components/List/List.stories.tsx b/lib/components/List/List.stories.tsx index a561cd74..c8a2cfdc 100644 --- a/lib/components/List/List.stories.tsx +++ b/lib/components/List/List.stories.tsx @@ -1,10 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { Fragment, useState } from 'react'; - -import { MetaItem } from '../Meta'; -import { List, ListBase, ListItem } from './'; - -const sizes = ['lg', 'md', 'sm', 'xs']; +import { List } from './'; const meta = { title: 'List/List', diff --git a/lib/components/List/List.tsx b/lib/components/List/List.tsx index 8dd23d0b..01c3e810 100644 --- a/lib/components/List/List.tsx +++ b/lib/components/List/List.tsx @@ -1,19 +1,27 @@ import { ListBase, type ListSpacing, type ListTheme } from '../List'; import { ListItem, type ListItemProps } from './ListItem'; -import type { ListItemSize } from './ListItemBase'; +import type { ListItemColor, ListItemSize } from './ListItemBase'; export interface ListProps { - size?: ListItemSize; spacing?: ListSpacing; theme?: ListTheme; items?: ListItemProps[]; + defaultItemColor?: ListItemColor; + defaultItemSize?: ListItemSize; } -export const List = ({ theme, size = 'md', spacing = 'md', items = [] }: ListProps) => { +export const List = ({ theme, defaultItemSize, defaultItemColor, spacing = 'md', items = [] }: ListProps) => { return ( {items.map((item, index) => { - return ; + return ( + + ); })} ); diff --git a/lib/components/List/ListItem.stories.tsx b/lib/components/List/ListItem.stories.tsx index ef2fe296..a9a0303e 100644 --- a/lib/components/List/ListItem.stories.tsx +++ b/lib/components/List/ListItem.stories.tsx @@ -1,9 +1,20 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Fragment, useState } from 'react'; -import { Button, List, ListBase, ListItem, ListItemBase, ListItemHeader, MetaItem } from '../'; +import { + type AvatarGroupProps, + Button, + List, + ListBase, + ListItem, + ListItemBase, + ListItemHeader, + type ListItemProps, + MetaItem, +} from '../'; -const sizes = ['xl', 'lg', 'md', 'sm', 'xs']; +const colors = ['neutral', 'accent', 'transparent'] as ListItemProps['color'][]; +const sizes = ['xl', 'lg', 'md', 'sm', 'xs'] as ListItemProps['size'][]; const meta = { title: 'List/ListItem', @@ -14,7 +25,7 @@ const meta = { id: 'id', title: 'Title', description: 'Description', - size: 'md', + size: 'sm', }, } satisfies Meta; @@ -25,7 +36,7 @@ export const Default: Story = { args: {}, }; -export const MediaTypes = (args) => { +export const MediaTypes = (args: ListItemProps) => { return ( @@ -102,10 +113,10 @@ export const MediaTypes = (args) => { ); }; -export const Loading = (args) => { +export const Loading = (args: ListItemProps) => { return ( - {sizes?.map((size) => { + {sizes.map((size) => { return ( @@ -118,7 +129,7 @@ export const Loading = (args) => { ); }; -export const Controls = (args) => { +export const Controls = (args: ListItemProps) => { return ( @@ -134,9 +145,10 @@ export const Controls = (args) => { badge={{ label: 'Admin' }} linkIcon="chevron-right" menu={{ + id: 'menu', items: [ - { title: 'Innstillinger', icon: 'cog' }, - { title: 'Aktivitetslogg', icon: 'clock-dashed' }, + { id: 'settings', title: 'Innstillinger', icon: 'cog' }, + { id: 'log', title: 'Aktivitetslogg', icon: 'clock-dashed' }, ], }} /> @@ -157,7 +169,7 @@ export const Controls = (args) => { ); }; -export const Selectable = (args) => { +export const Selectable = (args: ListItemProps) => { return ( {sizes?.map((size) => { @@ -187,24 +199,29 @@ export const Selectable = (args) => { ); }; -export const Colors = (args) => { +export const Colors = (args: ListItemProps) => { return ( - - Default - - Accent + {colors.map((color) => { + return ( + + + {color} + + ); + })} ); }; -export const Sizes = (args) => { +export const Sizes = (args: ListItemProps) => { return ( {sizes?.map((size) => { return ( - + + {size} ); @@ -213,7 +230,7 @@ export const Sizes = (args) => { ); }; -export const Collapsible = (args) => { +export const Collapsible = (args: ListItemProps) => { const [expanded, setExpanded] = useState(false); const onToggle = () => { @@ -243,7 +260,7 @@ export const Collapsible = (args) => { }, ]; - const items = people?.map((item) => { + const items: ListItemProps[] = people.map((item, index) => { return { avatar: { ...item, @@ -252,12 +269,20 @@ export const Collapsible = (args) => { title: item?.name, description: item?.role, badge: { label: item?.rights }, - linkIcon: 'menu-elipsis-horizontal', + menu: { + id: 'menu' + index, + items: [ + { + id: 'item1', + title: 'Hallo', + }, + ], + }, }; }); - const avatarGroup = { - items: people?.map((item) => { + const avatarGroup: AvatarGroupProps = { + items: people.map((item) => { return { name: item?.name, type: 'person', @@ -275,7 +300,7 @@ export const Collapsible = (args) => { onClick={onToggle} as="button" /> - {expanded && } + {expanded && }
); }; diff --git a/lib/components/List/ListItem.tsx b/lib/components/List/ListItem.tsx index dcea316f..a5efe0de 100644 --- a/lib/components/List/ListItem.tsx +++ b/lib/components/List/ListItem.tsx @@ -7,6 +7,8 @@ import { ListItemBase, type ListItemBaseProps } from './ListItemBase'; import { ListItemHeader, type ListItemHeaderProps } from './ListItemHeader'; export interface ListItemProps extends ListItemBaseProps, ListItemHeaderProps { + /** List item id */ + id?: string; /** Custom className */ className?: string; /** Element is loading, display a placeholder */ diff --git a/lib/components/List/ListItemBase.tsx b/lib/components/List/ListItemBase.tsx index b929b72b..68df3a61 100644 --- a/lib/components/List/ListItemBase.tsx +++ b/lib/components/List/ListItemBase.tsx @@ -3,7 +3,7 @@ import styles from './listItemBase.module.css'; export type ListItemSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export type ListItemVariant = 'solid' | 'dotted'; -export type ListItemColor = 'subtle' | 'accent' | 'transparent'; +export type ListItemColor = 'neutral' | 'accent' | 'transparent'; export type ListItemShadow = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export interface ListItemBaseProps { @@ -24,7 +24,7 @@ export interface ListItemBaseProps { export const ListItemBase = ({ size, variant = 'solid', - color = 'subtle', + color = 'neutral', shadow = 'xs', loading, disabled, diff --git a/lib/components/List/ListItemHeader.stories.tsx b/lib/components/List/ListItemHeader.stories.tsx index 51d3b2d4..31926a5a 100644 --- a/lib/components/List/ListItemHeader.stories.tsx +++ b/lib/components/List/ListItemHeader.stories.tsx @@ -1,9 +1,18 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Fragment, useState } from 'react'; -import { Button, ListBase, ListItemHeader, MetaItem } from '../'; +import { + type AvatarGroupProps, + Button, + List, + ListBase, + ListItemHeader, + type ListItemHeaderProps, + type ListItemProps, + MetaItem, +} from '../'; -const sizes = ['lg', 'md', 'sm', 'xs']; +const sizes = ['lg', 'md', 'sm', 'xs'] as ListItemHeaderProps['size'][]; const meta = { title: 'List/ListItemHeader', @@ -11,7 +20,6 @@ const meta = { tags: ['autodocs'], parameters: {}, args: { - id: 'id', title: 'Title', description: 'Description', size: 'md', @@ -25,7 +33,7 @@ export const Default: Story = { args: {}, }; -export const MediaTypes = (args) => { +export const MediaTypes = (args: ListItemHeaderProps) => { return ( @@ -102,7 +110,7 @@ export const MediaTypes = (args) => { ); }; -export const Loading = (args) => { +export const Loading = (args: ListItemHeaderProps) => { return ( {sizes?.map((size) => { @@ -118,7 +126,7 @@ export const Loading = (args) => { ); }; -export const Controls = (args) => { +export const Controls = (args: ListItemHeaderProps) => { return ( @@ -134,9 +142,10 @@ export const Controls = (args) => { badge={{ label: 'Admin' }} linkIcon="chevron-right" menu={{ + id: 'menu', items: [ - { title: 'Innstillinger', icon: 'cog' }, - { title: 'Aktivitetslogg', icon: 'clock-dashed' }, + { id: 'settings', title: 'Innstillinger', icon: 'cog' }, + { id: 'log', title: 'Aktivitetslogg', icon: 'clock-dashed' }, ], }} /> @@ -157,7 +166,7 @@ export const Controls = (args) => { ); }; -export const Selectable = (args) => { +export const Selectable = (args: ListItemHeaderProps) => { return ( {sizes?.map((size) => { @@ -187,30 +196,24 @@ export const Selectable = (args) => { ); }; -export const Colors = (args) => { +export const Colors = (args: ListItemHeaderProps) => { return ( Default - + Accent ); }; -export const Sizes = (args) => { +export const Sizes = (args: ListItemHeaderProps) => { return ( {sizes?.map((size) => { return ( - + {size} ); @@ -219,7 +222,7 @@ export const Sizes = (args) => { ); }; -export const Collapsible = (args) => { +export const Collapsible = (args: ListItemHeaderProps) => { const [expanded, setExpanded] = useState(false); const onToggle = () => { @@ -249,7 +252,7 @@ export const Collapsible = (args) => { }, ]; - const items = people?.map((item) => { + const items: ListItemProps[] = people.map((item) => { return { avatar: { ...item, @@ -262,8 +265,8 @@ export const Collapsible = (args) => { }; }); - const avatarGroup = { - items: people?.map((item) => { + const avatarGroup: AvatarGroupProps = { + items: people.map((item) => { return { name: item?.name, type: 'person', @@ -281,7 +284,7 @@ export const Collapsible = (args) => { onClick={onToggle} as="button" /> - {expanded && } + {expanded && } ); }; diff --git a/lib/components/List/ListItemHeader.tsx b/lib/components/List/ListItemHeader.tsx index 6d2893bb..8f3d250f 100644 --- a/lib/components/List/ListItemHeader.tsx +++ b/lib/components/List/ListItemHeader.tsx @@ -1,30 +1,22 @@ import cx from 'classnames'; -import type { ElementType, KeyboardEvent, KeyboardEventHandler, ReactNode } from 'react'; +import type { ReactNode } from 'react'; import type { AvatarGroupProps, AvatarProps } from '../Avatar'; import type { BadgeProps } from '../Badge'; import type { IconName } from '../Icon'; -import type { ListItemColor, ListItemSize } from './ListItemBase'; +import type { ListItemSize } from './ListItemBase'; import { ListItemControls } from './ListItemControls'; import { ListItemLabel } from './ListItemLabel'; +import { ListItemLink, type ListItemLinkProps } from './ListItemLink'; import { ListItemMedia } from './ListItemMedia'; import { ListItemMenu, type ListItemMenuProps } from './ListItemMenu'; import { ListItemSelect, type ListItemSelectProps } from './ListItemSelect'; import styles from './listItemHeader.module.css'; -export interface ListItemHeaderProps { - as?: ElementType; - href?: string; - onClick?: () => void; - onKeyPress?: KeyboardEventHandler; - tabIndex?: number; +export interface ListItemHeaderProps extends ListItemLinkProps { /** Header is loading */ loading?: boolean; - /** Header is disabled */ - disabled?: boolean; /** Header size */ size?: ListItemSize; - /** Optional color */ - color?: ListItemColor; /** Optional classname */ className?: string; /** Select controls */ @@ -69,7 +61,6 @@ export const ListItemHeader = ({ collapsible, expanded, size = 'sm', - color, title, description, icon, @@ -83,39 +74,28 @@ export const ListItemHeader = ({ className, children, }: ListItemHeaderProps) => { - const Component = as || 'button'; - const applicableLinkIcon = collapsible && expanded ? 'chevron-up' : collapsible ? 'chevron-down' : linkIcon; return ( -
+
{select && } - { - e.key === 'Enter' && onClick?.(); - onKeyPress?.(e); - }} onClick={onClick} + onKeyPress={onKeyPress} tabIndex={tabIndex} - data-size={size} - data-color={color} - aria-disabled={loading || disabled} + loading={loading} + disabled={disabled} + size={size} > - + {children} - + {(menu && ) || controls}
); diff --git a/lib/components/List/ListItemLink.tsx b/lib/components/List/ListItemLink.tsx new file mode 100644 index 00000000..58179577 --- /dev/null +++ b/lib/components/List/ListItemLink.tsx @@ -0,0 +1,52 @@ +import cx from 'classnames'; +import type { ElementType, KeyboardEvent, KeyboardEventHandler, ReactNode } from 'react'; +import type { ListItemSize } from './ListItemBase'; +import styles from './listItemLink.module.css'; + +export interface ListItemLinkProps { + size?: ListItemSize; + as?: ElementType; + href?: string; + onClick?: () => void; + onKeyPress?: KeyboardEventHandler; + tabIndex?: number; + loading?: boolean; + disabled?: boolean; + selected?: boolean; + className?: string; + children?: ReactNode; +} + +export const ListItemLink = ({ + size, + as, + loading, + disabled, + selected, + href, + onClick, + onKeyPress, + tabIndex, + className, + children, +}: ListItemLinkProps) => { + const Component = as || 'button'; + + return ( + { + e.key === 'Enter' && onClick?.(); + onKeyPress?.(e); + }} + onClick={onClick} + tabIndex={tabIndex} + aria-disabled={loading || disabled} + aria-selected={selected} + data-size={size} + > + {children} + + ); +}; diff --git a/lib/components/List/ListItemMedia.tsx b/lib/components/List/ListItemMedia.tsx index 58a855bb..0b20c25c 100644 --- a/lib/components/List/ListItemMedia.tsx +++ b/lib/components/List/ListItemMedia.tsx @@ -2,12 +2,11 @@ import type { ReactNode } from 'react'; import { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type AvatarSize } from '../Avatar'; import { Icon, type IconName } from '../Icon'; import { Skeleton } from '../Skeleton'; -import type { ListItemColor, ListItemSize } from './ListItemBase'; +import type { ListItemSize } from './ListItemBase'; import styles from './listItemMedia.module.css'; export interface ListItemMediaProps { loading?: boolean; - color?: ListItemColor; size?: ListItemSize; icon?: IconName; avatar?: AvatarProps; @@ -17,30 +16,22 @@ export interface ListItemMediaProps { const sizeMap = { avatar: { - xs: 'xs', + xs: 'sm', sm: 'sm', md: 'md', - lg: 'lg', - xl: 'xl', + lg: 'md', + xl: 'lg', }, avatarGroup: { - xs: 'xs', - sm: 'xs', - md: 'sm', + xs: 'sm', + sm: 'sm', + md: 'md', lg: 'md', xl: 'lg', }, }; -export const ListItemMedia = ({ - loading, - size = 'sm', - color = 'transparent', - icon, - avatar, - avatarGroup, - children, -}: ListItemMediaProps) => { +export const ListItemMedia = ({ loading, size = 'sm', icon, avatar, avatarGroup, children }: ListItemMediaProps) => { if (!icon && !avatar && !avatarGroup && !children) { return false; } @@ -54,7 +45,7 @@ export const ListItemMedia = ({ } return ( -
+
{(icon && ) || (avatar && ) || (avatarGroup && ( diff --git a/lib/components/List/index.ts b/lib/components/List/index.ts index 7d1fd9c6..839ee95f 100644 --- a/lib/components/List/index.ts +++ b/lib/components/List/index.ts @@ -1,4 +1,5 @@ export * from './ListItemBase'; +export * from './ListItemLink'; export * from './ListItemHeader'; export * from './ListItemLabel'; export * from './ListItemMedia'; diff --git a/lib/components/List/listItemBase.module.css b/lib/components/List/listItemBase.module.css index 9bcc37fc..d07040aa 100644 --- a/lib/components/List/listItemBase.module.css +++ b/lib/components/List/listItemBase.module.css @@ -5,18 +5,12 @@ flex-direction: column; } -.item[aria-disabled="true"] { - pointer-events: none; -} +/* shadow */ .item[data-shadow="xs"] { box-shadow: var(--ds-shadow-xs); } -.item:not([aria-disabled="true"]):hover { - outline: 2px solid var(--theme-border-strong); -} - /* variant */ .item[data-variant="outline"], @@ -34,17 +28,10 @@ /* subtle */ -.item[data-color="subtle"] { +.item[data-color="neutral"] { background-color: var(--theme-background-default); } -/* -.item[data-color="default"]:not([aria-disabled="true"], [aria-selected="true"]):hover { - background-color: rgba(255, 255, 255, 0.5); -}*/ - -/* surface color */ - .item[data-color="accent"] { background-color: var(--theme-surface-default); } @@ -64,6 +51,14 @@ } .item[aria-selected="true"] { - background-color: var(--theme-background-subtle); outline: 2px solid; } + +.item[aria-disabled="true"] { + pointer-events: none; +} + +.item:not([aria-selected="true"]):hover, +.item:not([aria-disabled="true"]):hover { + outline: 2px solid var(--theme-border-strong); +} diff --git a/lib/components/List/listItemHeader.module.css b/lib/components/List/listItemHeader.module.css index bc2b645c..68ff7673 100644 --- a/lib/components/List/listItemHeader.module.css +++ b/lib/components/List/listItemHeader.module.css @@ -5,57 +5,32 @@ align-items: center; } -.link { - width: 100%; - align-self: stretch; - padding: 0; - border: 0; - background-color: transparent; - color: inherit; - font: inherit; - text-align: inherit; - text-decoration: none; - line-height: normal; - -webkit-font-smoothing: inherit; - -moz-osx-font-smoothing: inherit; - appearance: none; - user-select: none; - cursor: pointer; -} - .link { flex-grow: 1; display: flex; align-items: center; column-gap: 0.625rem; - padding: 0.25rem 0.625rem; -} - -.link[aria-disabled="true"] { - pointer-events: none; -} - -.link:not([aria-disabled="true"]):hover h2 { - text-decoration: underline; + padding: 0.325rem 0.625rem; } .link[data-size="xs"] { - min-height: 36px; + min-height: 44px; } .link[data-size="sm"] { - min-height: 44px; + min-height: 52px; } .link[data-size="md"] { - min-height: 56px; - padding: 0.325rem 0.625rem; + min-height: 60px; + padding: 0.5rem 0.75rem; + column-gap: 0.75rem; } .link[data-size="lg"] { min-height: 64px; - padding: 0.5rem 0.75rem; - column-gap: 0.75rem; + padding: 0.5rem 0.875rem; + column-gap: 0.875rem; } .link[data-size="xl"] { diff --git a/lib/components/List/listItemLabel.module.css b/lib/components/List/listItemLabel.module.css index 8e8de386..90e5bb84 100644 --- a/lib/components/List/listItemLabel.module.css +++ b/lib/components/List/listItemLabel.module.css @@ -4,8 +4,7 @@ width: 100%; } -.label[data-size="xs"], -.label[data-size="sm"] { +.label[data-size="xs"] { display: block; white-space: nowrap; overflow: hidden; @@ -13,18 +12,10 @@ max-width: 100%; } -.label[data-size="md"] { - display: flex; - flex-direction: column; - row-gap: 0.125rem; -} - -.label[data-size="lg"] { - display: flex; - flex-direction: column; - row-gap: 0.125rem; -} - +.label[data-size="sm"], +.label[data-size="md"], +.label[data-size="md"], +.label[data-size="lg"], .label[data-size="xl"] { display: flex; flex-direction: column; @@ -57,7 +48,7 @@ } .title[data-size="md"] { - font-size: 1rem; + font-size: 1.125rem; } .title[data-size="lg"] { @@ -68,17 +59,12 @@ font-size: 1.25rem; } -.description[data-size="xs"] { - font-size: 0.875rem; -} - -.description[data-size="sm"] { - font-size: 0.875rem; -} - +.description[data-size="xs"], +.description[data-size="sm"], .description[data-size="md"], .description[data-size="lg"] { font-size: 0.875rem; + line-height: 1rem; } .description[data-size="xl"] { diff --git a/lib/components/List/listItemLink.module.css b/lib/components/List/listItemLink.module.css new file mode 100644 index 00000000..d909ff01 --- /dev/null +++ b/lib/components/List/listItemLink.module.css @@ -0,0 +1,23 @@ +.link { + padding: 0; + border: 0; + background-color: transparent; + color: inherit; + font: inherit; + text-align: inherit; + text-decoration: none; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; + appearance: none; + user-select: none; + cursor: pointer; +} + +.link[aria-disabled="true"] { + pointer-events: none; +} + +.link:not([aria-disabled="true"]):hover h2 { + text-decoration: underline; +} diff --git a/lib/components/List/listItemMedia.module.css b/lib/components/List/listItemMedia.module.css index f9c7809f..51fd57b0 100644 --- a/lib/components/List/listItemMedia.module.css +++ b/lib/components/List/listItemMedia.module.css @@ -13,28 +13,18 @@ font-size: 1.5rem; } -.media[data-size="xs"] { - font-size: 1.25rem; -} - -.media[data-size="md"] { - font-size: 1.875rem; -} - -.media[data-size="md"] .icon { - font-size: 1.5rem; -} - +.media[data-size="md"], .media[data-size="lg"] { - font-size: 2.25rem; + font-size: 1.875rem; } +.media[data-size="md"] .icon, .media[data-size="lg"] .icon { font-size: 1.75rem; } .media[data-size="xl"] { - font-size: 2.75rem; + font-size: 2.25rem; } .media[data-size="xl"] .icon { diff --git a/lib/components/List/listItemSelect.module.css b/lib/components/List/listItemSelect.module.css index 9da54e4e..68e640ac 100644 --- a/lib/components/List/listItemSelect.module.css +++ b/lib/components/List/listItemSelect.module.css @@ -25,13 +25,6 @@ margin-right: -0.5rem; } -.label[data-size="xs"] { - font-size: 1rem; - padding: .5rem; - margin-left: 0.25rem; - margin-right: -0.5rem; -} - .label:hover { background-color: var(--theme-surface-default); } diff --git a/lib/components/ResourceList/ResourceList.stories.tsx b/lib/components/ResourceList/ResourceList.stories.tsx index 92cbae21..eda8d09a 100644 --- a/lib/components/ResourceList/ResourceList.stories.tsx +++ b/lib/components/ResourceList/ResourceList.stories.tsx @@ -18,10 +18,10 @@ const meta = { args: { spacing: 'md', items, - size: 'md', + defaultItemSize: 'md', }, argTypes: { - size: { + defaultItemSize: { options: ['sm', 'md', 'lg'], control: { type: 'inline-radio', diff --git a/lib/components/ResourceList/ResourceList.tsx b/lib/components/ResourceList/ResourceList.tsx index 0dd992d7..82fd9b22 100644 --- a/lib/components/ResourceList/ResourceList.tsx +++ b/lib/components/ResourceList/ResourceList.tsx @@ -5,11 +5,11 @@ export interface ResourceListProps extends Omit { items: ResourceListItemProps[]; } -export const ResourceList = ({ items, size = 'md', ...props }: ResourceListProps) => { +export const ResourceList = ({ items, defaultItemSize = 'md', ...props }: ResourceListProps) => { return ( {items.map((item) => ( - + ))} ); diff --git a/lib/components/ResourceList/ResourceListItem.stories.tsx b/lib/components/ResourceList/ResourceListItem.stories.tsx index ec2f0900..8d897221 100644 --- a/lib/components/ResourceList/ResourceListItem.stories.tsx +++ b/lib/components/ResourceList/ResourceListItem.stories.tsx @@ -1,5 +1,4 @@ import type { Meta, StoryObj } from '@storybook/react'; -import React from 'react'; import { Button } from '../Button'; import { ResourceListItem } from './ResourceListItem'; diff --git a/lib/components/Searchbar/Searchbar.stories.tsx b/lib/components/Searchbar/Searchbar.stories.tsx index 134b1e12..71e88f0f 100644 --- a/lib/components/Searchbar/Searchbar.stories.tsx +++ b/lib/components/Searchbar/Searchbar.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { useState } from 'react'; -import { Searchbar } from './Searchbar'; +import { type ChangeEvent, useState } from 'react'; +import type { AutocompleteProps } from '../Autocomplete'; +import type { AutocompleteItemProps } from '../Autocomplete/AutocompleteItem'; +import { Searchbar, type SearchbarProps } from './Searchbar'; const meta = { title: 'Header/Searchbar', @@ -76,10 +78,10 @@ export const Expanded: Story = { }, }, }; -export const ControlledState = (args) => { +export const ControlledState = (args: SearchbarProps) => { const [q, setQ] = useState(''); const [searchOpen, setSearchOpen] = useState(false); - const onChange = (event) => { + const onChange = (event: ChangeEvent) => { setQ(event.target.value); }; @@ -87,7 +89,7 @@ export const ControlledState = (args) => { setQ(''); }; - const scopes = [ + const scopes: AutocompleteItemProps[] = [ { id: 'inbox', label: q @@ -120,13 +122,15 @@ export const ControlledState = (args) => { }; }); - const suggestions = q + const suggestions: AutocompleteItemProps[] = q ? [ { + type: 'dialog', href: '#skatt-2024', title: 'Skattemelding 2024', }, { + type: 'dialog', href: '#skatt-2024', title: 'Skattemelding 2025', }, @@ -141,16 +145,16 @@ export const ControlledState = (args) => { }) : []; - const autocompleteItems = [...scopes, ...suggestions].map((item) => { + const autocompleteItems: AutocompleteItemProps[] = [...scopes, ...suggestions].map((item) => { return { ...item, onClick: () => { - console.log(JSON.stringify(item)); + console.info('clicked', JSON.stringify(item)); }, }; }); - const autocomplete = { + const autocomplete: AutocompleteProps = { groups: { 2: { title: `${suggestions.length} treff i innboksen`, diff --git a/package.json b/package.json index 658d24e7..5baff03e 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,10 @@ "version": "0.10.0", "main": "dist/index.js", "types": "dist/index.d.ts", - "files": [ - "dist", - "!dist/stories", - "!dist/examples" - ], + "files": ["dist", "!dist/stories", "!dist/examples"], "type": "module", "description": "Reusable react components", - "sideEffects": [ - "*.css" - ], + "sideEffects": ["*.css"], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "storybook": "storybook dev -p 6006",