diff --git a/.storybook/styles.css b/.storybook/styles.css index bdba9fb..ae35084 100644 --- a/.storybook/styles.css +++ b/.storybook/styles.css @@ -1,7 +1,3 @@ .docblock-argstable-body label { min-height: auto; } - -.sbdocs p { - max-width: 30rem; -} diff --git a/components/composition/Grid/Grid.css b/components/composition/Grid/Grid.css index cc97422..58e02e7 100644 --- a/components/composition/Grid/Grid.css +++ b/components/composition/Grid/Grid.css @@ -85,6 +85,10 @@ diamond-grid { --diamond-grid-gap: 0; } + &[gap='xs'] { + --diamond-grid-gap: var(--diamond-spacing-xs); + } + &[gap='sm'] { --diamond-grid-gap: var(--diamond-spacing-sm); } diff --git a/components/content/Icon/Icon.css b/components/content/Icon/Icon.css new file mode 100644 index 0000000..5325a20 --- /dev/null +++ b/components/content/Icon/Icon.css @@ -0,0 +1,42 @@ +diamond-icon { + align-items: baseline; + color: var(--diamond-theme-icon-color); + display: inline-flex; + + &::part(icon) { + align-items: center; + display: flex; + height: 1em; + justify-content: center; + position: relative; + top: var(--diamond-icon-baseline-adjust); + width: var(--diamond-icon-size); + } + + svg { + aspect-ratio: 1 / 1; + display: block; + flex: 0 0 var(--diamond-icon-size); + height: var(--diamond-icon-size); + min-width: var(--diamond-icon-size); + width: var(--diamond-icon-size); + } + + &[variant='circle'] { + &::part(icon)::before { + background-color: var(--diamond-theme-icon-color); + border-radius: 100%; + content: ''; + display: block; + height: var(--diamond-icon-size); + position: absolute; + width: var(--diamond-icon-size); + } + + svg { + color: var(--diamond-theme-background); + transform: scale(0.75); + z-index: 1; + } + } +} diff --git a/components/content/Icon/Icon.stories.ts b/components/content/Icon/Icon.stories.ts new file mode 100644 index 0000000..765b2ee --- /dev/null +++ b/components/content/Icon/Icon.stories.ts @@ -0,0 +1,286 @@ +import { StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; + +import './Icon'; + +const description = ` +Wraps an svg icon to provide alignment and sizing. + +Icons must have the appropriate fill or stroke set to currentColor. + +Icons are hidden from screen readers by default and should be accompanied by text. If an icon is used on its own in a button or link, add a label prop to the component. +`; + +export default { + component: 'diamond-icon', + parameters: { + docs: { + description: { + component: description, + }, + }, + }, + argTypes: { + variant: { + control: { + type: 'radio', + }, + options: ['default', 'circle'], + }, + label: { + control: { + type: 'text', + }, + description: 'Aria label for the icon, if used without text.', + }, + }, +}; + +export const Icon: StoryObj = { + render: (args) => html` + + + + + + This is some text next to the icon + `, +}; + +Icon.args = { + variant: 'default', +}; + +const sizes = ['h1', 'h2', 'h3', 'h4', 'md', 'default', 'sm', 'xs']; + +export const Sizes: StoryObj = { + render: () => html` + ${sizes.map( + (size) => html` +

+ + + + + + The quick brown fox jumps over the lazy dog. +

+ `, + )} + `, +}; + +Sizes.parameters = { + docs: { + description: { + story: + 'Icons inherit their size from the current text size or can be sized using text size classes.', + }, + }, +}; + +export const IconInAGrid: StoryObj = { + render: () => html` + + + + + + + + + +

Title text

+
+
+ + + + + + + + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec + vulputate vel magna id varius. Donec non auctor magna, id auctor + lacus. Vestibulum vitae ex id tortor varius fringilla. Cras in ligula + vulputate, lacinia metus vitae, suscipit purus. Nunc lacinia porta + urna eget accumsan. Pellentesque quis condimentum est. Aliquam leo + risus, blandit vel volutpat et, accumsan eget purus. +

+
+
+ `, +}; + +IconInAGrid.parameters = { + docs: { + description: { + story: + 'Icons in a grid can be aligned to the text with the baseline alignment.', + }, + }, +}; + +const themes = ['light', 'medium', 'dark']; + +export const IconColors: StoryObj = { + render: () => html` + ${themes.map( + (theme) => html` + + +

+ + + + + + This is a ${theme} theme +

+
+
+ `, + )} + `, +}; + +IconColors.parameters = { + docs: { + description: { + story: 'Icon colours are inherited from the current theme.', + }, + }, +}; + +export const DifferentIconLibraries: StoryObj = { + render: () => html` +

+ + + + + + + Material symbols + +

+ +

+ + + + + + Hero icons +

+ +

+ + + + + + Bootstrap icons +

+ +

+ + + + + + Mono icons +

+ +

+ + + + + + + Feather icons +

+ `, +}; + +DifferentIconLibraries.parameters = { + docs: { + description: { + story: + 'You can use SVGs from any icon library. This story tests a few common libraries for alignment.', + }, + }, +}; diff --git a/components/content/Icon/Icon.ts b/components/content/Icon/Icon.ts new file mode 100644 index 0000000..e4ce591 --- /dev/null +++ b/components/content/Icon/Icon.ts @@ -0,0 +1,44 @@ +import { LitElement, html, nothing } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +import { JSXCustomElement } from '../../../types/jsx-custom-element'; + +export interface IconAttributes { + variant: 'default' | 'circle'; + label: string; +} + +@customElement('diamond-icon') +export class Icon extends LitElement { + @property({ reflect: true }) variant?: IconAttributes['variant']; + @property() label: IconAttributes['label'] = ''; + + render() { + const { label } = this; + + return html` +
+ +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'diamond-icon': IconAttributes; + } +} + +declare module 'react' { + namespace JSX { + interface IntrinsicElements { + 'diamond-icon': JSXCustomElement; + } + } +} diff --git a/components/content/List/List.css b/components/content/List/List.css new file mode 100644 index 0000000..a4e76b5 --- /dev/null +++ b/components/content/List/List.css @@ -0,0 +1,73 @@ +diamond-list { + --diamond-list-spacing: 0; + + li, + dl > div { + margin-bottom: var(--diamond-list-spacing); + + &:last-child { + margin-bottom: 0; + } + } + + &[variant='unstyled'] { + ul, + ol, + dl { + list-style-type: none; + margin: 0; + padding: 0; + } + } + + &[variant='icon'] { + ul, + ol { + list-style-type: none; + margin-left: 0; + padding-left: 0; + } + + li, + dl > div { + padding-left: var(--diamond-icon-size); + } + + diamond-icon { + /* The 4px handles inline block magic spacing */ + margin-left: calc(-1 * (var(--diamond-icon-size) + 4px)); + } + } + + &[spacing='xs'] { + --diamond-list-spacing: var(--diamond-spacing-xs); + } + + &[spacing='sm'] { + --diamond-list-spacing: var(--diamond-spacing-sm); + } + + &[spacing='md'] { + --diamond-list-spacing: var(--diamond-spacing-md); + } + + &[spacing='lg'] { + --diamond-list-spacing: var(--diamond-spacing-lg); + } + + &[spacing='xl'] { + --diamond-list-spacing: var(--diamond-spacing-xl); + } + + &[spacing='fluid'] { + --diamond-list-spacing: var(--diamond-spacing-fluid); + } + + &[spacing='fluid-sm'] { + --diamond-list-spacing: var(--diamond-spacing-fluid-sm); + } + + &[spacing='fluid-lg'] { + --diamond-list-spacing: var(--diamond-spacing-fluid-lg); + } +} diff --git a/components/content/List/List.stories.ts b/components/content/List/List.stories.ts new file mode 100644 index 0000000..56727b4 --- /dev/null +++ b/components/content/List/List.stories.ts @@ -0,0 +1,179 @@ +import { StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; + +import '../Icon/Icon'; +import './List'; + +export default { + component: 'diamond-list', + argTypes: { + variant: { + control: { + type: 'radio', + }, + options: ['unstyled', 'icon'], + }, + spacing: { + control: { + type: 'select', + }, + options: [ + 'none', + 'xs', + 'sm', + 'md', + 'lg', + 'xl', + 'fluid-sm', + 'fluid', + 'fluid-lg', + ], + }, + }, +}; + +export const List: StoryObj = { + render: (args) => html` + + + + `, +}; + +List.args = { + spacing: 'none', +}; + +export const Unstyled: StoryObj = { + render: (args) => html` + + + + `, +}; + +Unstyled.args = { + variant: 'unstyled', +}; + +const unstyledDescription = ` +Removes the list styles. This is useful for semantic lists that we don't want to see visually. + +**Note**: Safari does not recognize ordered or unordered lists as lists in the accessibility tree if they have a list-style value of none, unless the list is nested within the