From 25e0c90dc6e9af45661d358b0710900701ca936a Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 2 May 2024 05:22:52 +0100 Subject: [PATCH 1/5] feat: icon component --- components/content/Icon/Icon.css | 42 ++++ components/content/Icon/Icon.stories.ts | 254 ++++++++++++++++++++++++ components/content/Icon/Icon.ts | 35 ++++ styles/tokens.css | 1 + styles/tokens/icon.css | 7 + 5 files changed, 339 insertions(+) create mode 100644 components/content/Icon/Icon.css create mode 100644 components/content/Icon/Icon.stories.ts create mode 100644 components/content/Icon/Icon.ts create mode 100644 styles/tokens/icon.css diff --git a/components/content/Icon/Icon.css b/components/content/Icon/Icon.css new file mode 100644 index 0000000..ac8e0f0 --- /dev/null +++ b/components/content/Icon/Icon.css @@ -0,0 +1,42 @@ +diamond-icon { + --diamond-icon-size: 1.25em; + align-items: baseline; + 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); + } + + &[shape='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..d7a155d --- /dev/null +++ b/components/content/Icon/Icon.stories.ts @@ -0,0 +1,254 @@ +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 must be used on its own, add an aria-label to the icon element. +`; + +export default { + component: 'diamond-icon', + parameters: { + docs: { + description: { + component: description, + }, + }, + }, + argTypes: { + shape: { + control: { + type: 'radio', + }, + options: ['default', 'circle'], + }, + }, +}; + +export const Icon: StoryObj = { + render: (args) => html` + + + + + + This is some text next to the icon + `, +}; + +Icon.args = { + shape: '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

+
+
+ `, +}; + +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..b3778bd --- /dev/null +++ b/components/content/Icon/Icon.ts @@ -0,0 +1,35 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +import { JSXCustomElement } from '../../../types/jsx-custom-element'; + +export interface IconAttributes { + shape: 'default' | 'circle'; +} + +@customElement('diamond-icon') +export class Icon extends LitElement { + @property({ reflect: true }) shape?: IconAttributes['shape']; + + render() { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'diamond-icon': IconAttributes; + } +} + +declare module 'react' { + namespace JSX { + interface IntrinsicElements { + 'diamond-icon': JSXCustomElement; + } + } +} diff --git a/styles/tokens.css b/styles/tokens.css index 8fb88d4..14c7f8d 100644 --- a/styles/tokens.css +++ b/styles/tokens.css @@ -2,6 +2,7 @@ @import url('./tokens/button.css'); @import url('./tokens/color.css'); @import url('./tokens/font.css'); +@import url('./tokens/icon.css'); @import url('./tokens/input.css'); @import url('./tokens/label.css'); @import url('./tokens/radius.css'); diff --git a/styles/tokens/icon.css b/styles/tokens/icon.css new file mode 100644 index 0000000..6fce377 --- /dev/null +++ b/styles/tokens/icon.css @@ -0,0 +1,7 @@ +:root { + /* + Adjust the icon to sit nicely next to text, + probably needs changing per font + */ + --diamond-icon-baseline-adjust: 0.25em; +} From 1c2e6e0b3de704ded35adb7ec7b5ad6996c3de2d Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Thu, 2 May 2024 05:37:53 +0100 Subject: [PATCH 2/5] feat: list component --- .storybook/styles.css | 4 - components/composition/Grid/Grid.css | 4 + components/content/Icon/Icon.css | 1 - components/content/List/List.css | 70 +++++++++++ components/content/List/List.stories.ts | 147 ++++++++++++++++++++++++ components/content/List/List.ts | 29 +++++ styles/tokens/icon.css | 2 + 7 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 components/content/List/List.css create mode 100644 components/content/List/List.stories.ts create mode 100644 components/content/List/List.ts 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 index ac8e0f0..fffcf6f 100644 --- a/components/content/Icon/Icon.css +++ b/components/content/Icon/Icon.css @@ -1,5 +1,4 @@ diamond-icon { - --diamond-icon-size: 1.25em; align-items: baseline; display: inline-flex; diff --git a/components/content/List/List.css b/components/content/List/List.css new file mode 100644 index 0000000..4d72eb6 --- /dev/null +++ b/components/content/List/List.css @@ -0,0 +1,70 @@ +diamond-list { + --diamond-list-spacing: 0; + + li { + margin-bottom: var(--diamond-list-spacing); + + &:last-child { + margin-bottom: 0; + } + } + + &[variant='unstyled'] { + ul, + ol { + list-style-type: none; + margin: 0; + padding: 0; + } + } + + &[variant='icon'] { + ul, + ol { + list-style-type: none; + margin: 0; + padding: 0; + } + + li { + 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..a77a2fa --- /dev/null +++ b/components/content/List/List.stories.ts @@ -0,0 +1,147 @@ +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` + +
    +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
+
+ `, +}; + +List.args = { + spacing: 'none', +}; + +export const Unstyled: StoryObj = { + render: (args) => html` + +
    +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
+
+ `, +}; + +Unstyled.args = { + variant: 'unstyled', +}; + +Unstyled.parameters = { + docs: { + description: { + story: + "Removes the list styles. This is useful for semantic lists that we don't want to see visually.", + }, + }, +}; + +export const Icon: StoryObj = { + render: (args) => html` + +
    +
  • + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas + fermentum pharetra massa sed convallis. Mauris finibus justo sit amet + lorem scelerisque consectetur. +
  • +
  • + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas + fermentum pharetra massa sed convallis. Mauris finibus justo sit amet + lorem scelerisque consectetur. +
  • +
  • + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas + fermentum pharetra massa sed convallis. Mauris finibus justo sit amet + lorem scelerisque consectetur. +
  • +
+
+ `, +}; + +Icon.args = { + variant: 'icon', +}; diff --git a/components/content/List/List.ts b/components/content/List/List.ts new file mode 100644 index 0000000..ef53bf5 --- /dev/null +++ b/components/content/List/List.ts @@ -0,0 +1,29 @@ +import { JSXCustomElement } from '../../../types/jsx-custom-element'; + +export interface ListAttributes { + variant?: 'unstyled' | 'icon'; + spacing?: + | 'none' + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | 'fluid-sm' + | 'fluid' + | 'fluid-lg'; +} + +declare global { + interface HTMLElementTagNameMap { + 'diamond-list': ListAttributes; + } +} + +declare module 'react' { + namespace JSX { + interface IntrinsicElements { + 'diamond-list': JSXCustomElement; + } + } +} diff --git a/styles/tokens/icon.css b/styles/tokens/icon.css index 6fce377..a16bb2e 100644 --- a/styles/tokens/icon.css +++ b/styles/tokens/icon.css @@ -1,4 +1,6 @@ :root { + /* The size of icons compared to the text */ + --diamond-icon-size: 1.25em; /* Adjust the icon to sit nicely next to text, probably needs changing per font From 5953f78d04d57ca11c83abde729536353f0eb170 Mon Sep 17 00:00:00 2001 From: Gavyn McKenzie Date: Fri, 3 May 2024 04:16:01 +0100 Subject: [PATCH 3/5] refactor: pr feedback polish --- components/content/Icon/Icon.css | 3 ++- components/content/Icon/Icon.stories.ts | 36 +++++++++++++++++++++---- components/content/Icon/Icon.ts | 4 +-- components/content/List/List.stories.ts | 11 ++++++-- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/components/content/Icon/Icon.css b/components/content/Icon/Icon.css index fffcf6f..5325a20 100644 --- a/components/content/Icon/Icon.css +++ b/components/content/Icon/Icon.css @@ -1,5 +1,6 @@ diamond-icon { align-items: baseline; + color: var(--diamond-theme-icon-color); display: inline-flex; &::part(icon) { @@ -21,7 +22,7 @@ diamond-icon { width: var(--diamond-icon-size); } - &[shape='circle'] { + &[variant='circle'] { &::part(icon)::before { background-color: var(--diamond-theme-icon-color); border-radius: 100%; diff --git a/components/content/Icon/Icon.stories.ts b/components/content/Icon/Icon.stories.ts index d7a155d..05db1ff 100644 --- a/components/content/Icon/Icon.stories.ts +++ b/components/content/Icon/Icon.stories.ts @@ -8,7 +8,7 @@ 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 must be used on its own, add an aria-label to the icon element. +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 an aria-label to the interactive element. `; export default { @@ -21,7 +21,7 @@ export default { }, }, argTypes: { - shape: { + variant: { control: { type: 'radio', }, @@ -32,7 +32,7 @@ export default { export const Icon: StoryObj = { render: (args) => html` - + html` - + @@ -96,6 +100,28 @@ export const IconInAGrid: StoryObj = {

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. +

+
+
`, }; diff --git a/components/content/Icon/Icon.ts b/components/content/Icon/Icon.ts index b3778bd..25aa3a5 100644 --- a/components/content/Icon/Icon.ts +++ b/components/content/Icon/Icon.ts @@ -4,12 +4,12 @@ import { customElement, property } from 'lit/decorators.js'; import { JSXCustomElement } from '../../../types/jsx-custom-element'; export interface IconAttributes { - shape: 'default' | 'circle'; + variant: 'default' | 'circle'; } @customElement('diamond-icon') export class Icon extends LitElement { - @property({ reflect: true }) shape?: IconAttributes['shape']; + @property({ reflect: true }) variant?: IconAttributes['variant']; render() { return html` diff --git a/components/content/List/List.stories.ts b/components/content/List/List.stories.ts index a77a2fa..01870c1 100644 --- a/components/content/List/List.stories.ts +++ b/components/content/List/List.stories.ts @@ -64,11 +64,18 @@ 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