diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 0ee000e..5ea0402 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,4 +1,5 @@ module.exports = { + ignorePatterns: ['dist', 'node_modules'], plugins: ['lit-a11y', 'spellcheck'], extends: ['@etchteam', 'plugin:lit-a11y/recommended'], rules: { @@ -19,6 +20,33 @@ module.exports = { 'center', 'etc', 'flexbox', + 'StoryObj', + 'dmd', + 'obj', + 'i', + 'keyframes', + 'srgb', + 'alt', + 'xl', + 'xxl', + 'xxxl', + 'xxxxl', + 'sr', + 'textarea', + 'Textarea', + 'calc', + 'unstyled', + 'svg', + 'colors', + 'div', + 'formgroup', + 'dialog', + 'unobserve', + 'bp', + 'eee', + 'centers', + 'centered', + 'gs', ], }, ], diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b735373..f469f33 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,13 +8,13 @@ about: Create a report to help us improve A clear and concise description of what the bug is. **To Reproduce** -Steps to reproduce the behavior: +Steps to reproduce the behaviour: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +**Expected behaviour** A clear and concise description of what you expected to happen. **Screenshots** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6eb38a0..8c826b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,4 +53,4 @@ jobs: VALIDATE_JSX: false VALIDATE_TSX: false VALIDATE_TYPESCRIPT_STANDARD: false - FILTER_REGEX_EXCLUDE: preview-head.html + FILTER_REGEX_EXCLUDE: (preview-head.html|CLAUDE.md|bug_report.md) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index fab1704..3905979 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -122,21 +122,21 @@ const preview: Preview = { }, designTokenTables: { collections: { - 'diamond-radius': 'radius', - 'diamond-color': 'color', - 'diamond-spacing': 'spacing', - 'diamond-button-gap': 'spacing', - 'diamond-font-line-height': 'line-height', - 'diamond-font-size': 'font-size', - 'diamond-font-weight': 'font-weight', - 'diamond-theme': 'color', - 'diamond-button-primary': 'color', - 'diamond-button-secondary': 'color', - 'diamond-button-text': 'color', - 'diamond-font-family': 'font-family', - 'diamond-input-radio-checkbox-padding': 'spacing', - 'diamond-input-padding': 'spacing', - 'diamond-shadow': 'shadow', + 'dmd-radius': 'radius', + 'dmd-color': 'color', + 'dmd-spacing': 'spacing', + 'dmd-button-gap': 'spacing', + 'dmd-font-line-height': 'line-height', + 'dmd-font-size': 'font-size', + 'dmd-font-weight': 'font-weight', + 'dmd-theme': 'color', + 'dmd-button-primary': 'color', + 'dmd-button-secondary': 'color', + 'dmd-button-text': 'color', + 'dmd-font-family': 'font-family', + 'dmd-input-radio-checkbox-padding': 'spacing', + 'dmd-input-padding': 'spacing', + 'dmd-shadow': 'shadow', }, tokens: [ borderTokens, diff --git a/.storybook/styles.css b/.storybook/styles.css index 771dc71..1645578 100644 --- a/.storybook/styles.css +++ b/.storybook/styles.css @@ -3,10 +3,10 @@ } docs-placeholder { - background: var(--diamond-color-grey-50); - border: dotted 1px var(--diamond-theme-border-color); - border-radius: var(--diamond-radius-sm); + background: var(--dmd-color-grey-50); + border: dotted 1px var(--dmd-theme-border-color); + border-radius: var(--dmd-radius-sm); display: block; - padding: var(--diamond-spacing-lg); + padding: var(--dmd-spacing-lg); text-align: left; } diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2b267ec --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,149 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Diamond UI is a lightweight design system built with Lit web components, design tokens, and CSS. Components use minimal JavaScript and follow the "CSS web components" methodology (see [https://etch.co/blog/css-web-components](https://etch.co/blog/css-web-components)). The library is framework-agnostic and can be installed in any web project. + +**Component naming convention**: All components are prefixed with `dmd-` (abbreviated from "diamond"). For example: `dmd-grid`, `dmd-card`, `dmd-button`. + +## Development Commands + +### Running the project +```bash +npm start # Start Vite dev server +npm run storybook # Start Storybook on port 6006 +``` + +### Building +```bash +npm run build # Build components and styles with Rollup +npm run build-storybook # Build Storybook static site +``` + +### Code quality +```bash +npm run lint:css # Lint CSS files with Stylelint +npm run lint:js # Lint JS with eslint +``` + +Note: There are no test scripts configured yet (`npm test` will fail). + +## Architecture + +### Component Categories + +Components are organized into four categories under `components/`: + +1. **canvas/** - Layout containers (e.g., `Card`, `Section`) +2. **composition/** - Layout and structural components (e.g., `Grid`, `FormGroup`, `Dialog`, `Hidden`) +3. **content/** - Content display components (e.g., `Icon`, `Text`, `List`, `LoadingButton`) +4. **control/** - Interactive form components (e.g., `Button`, `Input`, `RadioCheckbox`, `Link`) + +### Component Types + +Diamond UI uses two component approaches: + +1. **Lit Components** - JavaScript web components built with Lit + - Located in `.ts` files + - Use `@customElement` decorator + - Extend `LitElement` + - Example: `Icon`, `LoadingButton` + +2. **CSS Web Components** - Pure CSS components with only TypeScript interfaces + - Have a `.ts` file that ONLY exports TypeScript types/interfaces (no implementation) + - All styling and behaviour in corresponding `.css` file + - Example: `Card`, `Button`, `Grid` + +### Component Structure Pattern + +Each component follows this structure: + +```text +components/[category]/[ComponentName]/ +├── ComponentName.ts # Lit component OR type definitions only +├── ComponentName.css # Component styles (always present) +└── ComponentName.stories.ts # Storybook stories +``` + +**Key files:** +- `ComponentName.ts` exports an interface `[ComponentName]Attributes` defining the component's props +- All components declare global types for both vanilla HTML and React JSX usage +- React type declarations use `JSXCustomElement` helper type from `types/jsx-custom-element.ts` + +Example interface pattern: +```typescript +export interface CardAttributes { + border?: string | boolean; + padding?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none'; +} + +declare global { + interface HTMLElementTagNameMap { + 'dmd-card': CardAttributes; + } +} + +declare module 'react' { + namespace JSX { + interface IntrinsicElements { + 'dmd-card': JSXCustomElement; + } + } +} +``` + +### Design Tokens + +All design tokens are defined as CSS custom properties in `styles/tokens/`: +- `border.css` - Border styles +- `button.css` - Button-specific tokens +- `color.css` - Color palette +- `font.css` - Typography +- `icon.css` - Icon sizing +- `input.css` - Form input styles +- `radius.css` - Border radius values +- `shadow.css` - Box shadows +- `spacing.css` - Spacing scale +- `theme.css` - Theme variables +- `transition.css` - Animation timings +- `wrap.css` - Container widths + +Tokens can be overridden by defining CSS custom properties on `:root`. + +### Build Tool + +Rollup is configured to output three bundles: +1. **JavaScript components** - All `.ts` files from `components/` (excluding `.stories.ts`) +2. **Type definitions** - Generated `.d.ts` files +3. **Styles** - `diamond-ui.css` bundle that includes all component styles via PostCSS glob imports + +The build: +- Uses `@rollup/plugin-typescript` for TypeScript compilation +- Uses `rollup-plugin-postcss` with `postcss-import-ext-glob` to bundle CSS +- Maintains directory structure in `dist/` +- Copies individual token files to `dist/styles/` for granular imports + +### Shared Utilities + +Reusable code in `lib/`: +- `pulse.ts` - Lit CSS for loading/skeleton animations +- `breakpoints.ts` - Responsive breakpoint definitions +- `css-map.ts` - Utility for mapping props to CSS classes + +### Interactive Elements Pattern + +When custom elements need to be interactive (clickable/tappable), wrap them in semantic HTML elements (``, ` - - + + `, }; @@ -111,27 +111,27 @@ Interactive.parameters = { export const ImageCard: StoryObj = { render: () => html` - - - + + + Placeholder - +

Card title

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

- + - -
-
+ + + `, }; diff --git a/components/canvas/Card/Card.ts b/components/canvas/Card/Card.ts index b0e6b58..7b9a729 100644 --- a/components/canvas/Card/Card.ts +++ b/components/canvas/Card/Card.ts @@ -19,14 +19,14 @@ export interface CardAttributes { declare global { interface HTMLElementTagNameMap { - 'diamond-card': CardAttributes; + 'dmd-card': CardAttributes; } } declare module 'react' { namespace JSX { interface IntrinsicElements { - 'diamond-card': JSXCustomElement; + 'dmd-card': JSXCustomElement; } } } diff --git a/components/canvas/Section/Section.css b/components/canvas/Section/Section.css index 251c0d9..77779f9 100644 --- a/components/canvas/Section/Section.css +++ b/components/canvas/Section/Section.css @@ -1,35 +1,35 @@ -diamond-section { - background: var(--diamond-theme-background); - color: var(--diamond-theme-color); +dmd-section { + background: var(--dmd-theme-background); + color: var(--dmd-theme-color); display: block; - padding-block: var(--diamond-spacing); + padding-block: var(--dmd-spacing); &[padding='xs'] { - padding-block: var(--diamond-spacing-xs); + padding-block: var(--dmd-spacing-xs); } &[padding='sm'] { - padding-block: var(--diamond-spacing-sm); + padding-block: var(--dmd-spacing-sm); } &[padding='lg'] { - padding-block: var(--diamond-spacing-lg); + padding-block: var(--dmd-spacing-lg); } &[padding='xl'] { - padding-block: var(--diamond-spacing-xl); + padding-block: var(--dmd-spacing-xl); } &[padding='fluid-sm'] { - padding-block: var(--diamond-spacing-fluid-sm); + padding-block: var(--dmd-spacing-fluid-sm); } &[padding='fluid'] { - padding-block: var(--diamond-spacing-fluid); + padding-block: var(--dmd-spacing-fluid); } &[padding='fluid-lg'] { - padding-block: var(--diamond-spacing-fluid-lg); + padding-block: var(--dmd-spacing-fluid-lg); } &[padding='none'] { diff --git a/components/canvas/Section/Section.stories.ts b/components/canvas/Section/Section.stories.ts index 17a1bfa..ddfc9fb 100644 --- a/components/canvas/Section/Section.stories.ts +++ b/components/canvas/Section/Section.stories.ts @@ -18,7 +18,7 @@ text colour based on the current theme as well as vertical spacing. `; export default { - component: 'diamond-section', + component: 'dmd-section', parameters: { layout: 'fullscreen', docs: { @@ -55,30 +55,30 @@ export default { export const Section: StoryObj = { render: ({ padding, theme }) => html` - - + +

Section title

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

- - - + + + - - - - + + + + - - - -
-
+ + + + + `, }; @@ -92,30 +92,30 @@ export const Theming = { return html` ${['light', 'medium', 'dark'].map( (theme) => html` - - + +

Section title

-

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

- - - + + + - - - - + + + + - - - -
-
+ + + + + `, )} `; diff --git a/components/canvas/Section/Section.ts b/components/canvas/Section/Section.ts index a58557c..9e1a213 100644 --- a/components/canvas/Section/Section.ts +++ b/components/canvas/Section/Section.ts @@ -15,14 +15,14 @@ export interface SectionAttributes { declare global { interface HTMLElementTagNameMap { - 'diamond-section': SectionAttributes; + 'dmd-section': SectionAttributes; } } declare module 'react' { namespace JSX { interface IntrinsicElements { - 'diamond-section': JSXCustomElement; + 'dmd-section': JSXCustomElement; } } } diff --git a/components/composition/App/App.css b/components/composition/App/App.css index ef6e7b1..127b13c 100644 --- a/components/composition/App/App.css +++ b/components/composition/App/App.css @@ -1,4 +1,4 @@ -diamond-app { +dmd-app { display: grid; grid-template-areas: 'header' 'main' 'footer'; grid-template-columns: 100%; diff --git a/components/composition/App/App.stories.ts b/components/composition/App/App.stories.ts index 1d74372..6c6c179 100644 --- a/components/composition/App/App.stories.ts +++ b/components/composition/App/App.stories.ts @@ -4,7 +4,7 @@ import { html } from 'lit'; import './App'; export default { - component: 'diamond-app', + component: 'dmd-app', parameters: { layout: 'fullscreen', docs: { @@ -26,22 +26,22 @@ export default { export const App: StoryObj = { render: (args) => html` - - - + + +
Header
-
-
- - + + + +
Main
-
-
- - + + + +
Footer
-
-
-
+ + + `, }; diff --git a/components/composition/App/App.ts b/components/composition/App/App.ts index e201e1b..2de440e 100644 --- a/components/composition/App/App.ts +++ b/components/composition/App/App.ts @@ -7,7 +7,7 @@ export interface AppAttributes { header?: 'sticky'; } -@customElement('diamond-app') +@customElement('dmd-app') export class App extends LitElement { @property({ reflect: true }) header?: 'sticky'; @@ -28,14 +28,14 @@ export class App extends LitElement { declare global { interface HTMLElementTagNameMap { - 'diamond-app': AppAttributes; + 'dmd-app': AppAttributes; } } declare module 'react' { namespace JSX { interface IntrinsicElements { - 'diamond-app': JSXCustomElement; + 'dmd-app': JSXCustomElement; } } } diff --git a/components/composition/Collapse/Collapse.stories.ts b/components/composition/Collapse/Collapse.stories.ts index 9610904..73fcc34 100644 --- a/components/composition/Collapse/Collapse.stories.ts +++ b/components/composition/Collapse/Collapse.stories.ts @@ -11,7 +11,7 @@ The button to control the collapse must include aria-controls and aria-expanded `; export default { - component: 'diamond-collapse', + component: 'dmd-collapse', parameters: { docs: { description: { @@ -23,7 +23,7 @@ export default { export const Collapse: StoryObj = { render: () => html` - + - - + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse suscipit leo sodales sem sollicitudin maximus. @@ -47,14 +47,14 @@ export const Collapse: StoryObj = { Nulla id enim vehicula, varius leo et, sagittis libero. Nam vel dolor fringilla, viverra massa ut, pharetra enim. Praesent non varius nisl.

-
+ - - +

- Fake loading title + Fake loading title

- + Lorem ipsum dolor consequit sit amet epsilon - +

-
- + +
- + ${[1, 2, 3, 4].map( (i) => html` - - - - + + - - -

- - Card title - + > + +

+ Card title

-

- +

+ Lorem ipsum dolor consequit sit amet epsilon evitcus smartrum. - +

- - - Button text - - -
-
-
+ + Button text + + + + `, )} -
+
-
-
+ + `, }; diff --git a/docs/recipes/Theming.stories.ts b/docs/recipes/Theming.stories.ts index 6f50734..c8d6e70 100644 --- a/docs/recipes/Theming.stories.ts +++ b/docs/recipes/Theming.stories.ts @@ -19,34 +19,34 @@ export default { export const Theming: StoryObj = { render: () => html` - - - + + +

Card title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
-
- - + + + +

Card title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
-
- - + + + +

Card title

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
-
-
+ + + `, }; diff --git a/docs/showroom/Form.ts b/docs/showroom/Form.ts index f98ba2a..7b6c925 100644 --- a/docs/showroom/Form.ts +++ b/docs/showroom/Form.ts @@ -7,7 +7,7 @@ import '../../components/content/Icon/Icon'; import '../../components/control/RadioCheckbox/RadioCheckbox'; const infoIcon = html` - + - + `; const chevronDownIcon = html` - + - + `; const envelopeIcon = html` - + - + `; const infoButton = html` - + - + `; const checkIcon = html` - + - + `; export const Form: StoryObj = { render: () => html` - +
- - + + Logo - - + +
- - - + + - + Placeholder - - -

Big title, clever words

+ + +

Big title, clever words

Pellentesque posuere enim ex, vel rutrum ligula semper et. Donec vel nulla nibh.

-
-
+ + -
- - - -

+
+ + + +

Card title

- - ${infoButton} - -
- - +
+ ${infoButton} +
+
+ + ${checkIcon} Nulla ante eu consequat. - - - + + + - - - - + + + +
- - - -

+ + + +

Card title

- - ${infoButton} - -
+
+ ${infoButton} +
+
- - - + + - - ${infoIcon} - - + + ${infoIcon} + + ${chevronDownIcon} - - + + - - - + + - - ${infoIcon} - + + ${infoIcon} +
- + - - + + - +
-
+ - - + ${envelopeIcon} - - Help text - + + Help text + -
- + - - + + - +
-
+ -
- - - +
+ + + -
-
-
- + + + +
- - - -

+ + + +

Card title

- - ${infoButton} - -
- - - +
+ ${infoButton} +
+
+ + + - - - - + + + + - - - - + + + +
- - - -

+ + + +

Card title

- - ${infoButton} - +
+ ${infoButton} +
-
+
- - + +

Section title

-
- - + + + - - -
+ + + - - + ${chevronDownIcon} - - + + -
- + - - + + - +
-
+ -
- + - - + + - +
-
+ -
- - - +
+ + + -
-
- - + + + + - - -
- + + + +
- - - -

+ + + +

Card title

- - ${infoButton} - -
- +
+ ${infoButton} +
+
+ - - + +

- - - + + + - - - - - + + + + +

-
+ `, }; diff --git a/docs/showroom/Sales.ts b/docs/showroom/Sales.ts index 9640646..ba37f21 100644 --- a/docs/showroom/Sales.ts +++ b/docs/showroom/Sales.ts @@ -6,33 +6,33 @@ import '../../components/composition/App/App'; export const Sales: StoryObj = { render: () => html` - +
- - + + Logo - - + +
- - - - - + + + + + Eyebrow text

Big title, clever words

-
- +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. @@ -40,205 +40,198 @@ export const Sales: StoryObj = { Curabitur laoreet iaculis orci, id hendrerit odio consectetur eu.

-
- - + + - - + + - + ${[0, 1, 2, 3].map( (i) => html` - - + + Award - - + + `, )} - -
-
-
-
- - - - - + + + + + + + + +

Section title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut sapien eget nisl interdum porta vitae eu libero.

-
-
+ + - ${[0, 1, 2, 3].map( (i) => html` - - - - + + USP - +

USP title

Phasellus sodales quam fermentum enim lobortis, non pellentesque est malesuada.

-
-
-
+ + + `, )} -
+ - - - - + + + - - - - -
-
- - - - - + + + + + + + + + +

Section title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut sapien eget nisl interdum porta vitae eu libero.

-
-
+ + - ${[0, 1, 2].map( (i) => html` - - + - - + USP - +

USP title

-

+

Phasellus sodales quam fermentum enim lobortis, non pellentesque est malesuada.

- - + - + Link text - -
-
-
+ + + + `, )} -
-
-
- - - - - - + + + + + + + + + Placeholder - - - - + + + +

Section title

- +
    ${[0, 1, 2].map( () => html` @@ -250,28 +243,28 @@ export const Sales: StoryObj = { `, )}
-
-
- - + + + - - -
-
-
-
- - - - - - - + + + + + + + + + + + + +

Section title

Proin dapibus, quam nec ullamcorper suscipit, mauris ipsum @@ -283,169 +276,167 @@ export const Sales: StoryObj = { Cras dapibus vestibulum dapibus.

Vestibulum dapibus — Molestie -
-
-
-
-
-
- - - - - - + + + + + + + + + + + +

Section title

Etiam nec sagittis quam, eu vehicula felis. Sed vel iaculis velit. Suspendisse id congue orci. Integer rutrum bibendum vulputate.

-
-
+ + ${[0, 1, 2, 3].map( (i) => html` - - + - + Placeholder - - - + + + `, )} -
-
-
- - - - - + + + + + + +

Section title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut sapien eget nisl interdum porta vitae eu libero.

-
-
+ + - ${[0, 1, 2, 3].map( (i) => html` - - + - - - - + + + + ${[0, 1, 2, 3, 4].map( () => html` - + Placeholder - + `, )} - - - + + + Placeholder - - -
+ + +

Card title

-

+

Integer fermentum sed libero ac accumsan. Nullam et erat id erat ornare vestibulum et ac leo. Integer eu eros rhoncus, volutpat turpis nec, dignissim nisi. Suspendisse vel efficitur libero, eget finibus dui.

- - + + Placeholder - - + + Cras dapibus - - -
-
-
+ + + + + `, )} -
+ - - - - + + + - - - - -
-
- - - - - + + + + + + + + + + - -

Section title

-
+ +

Section title

+
${[0, 1, 2].map( () => html` - - + + - - + + `, )} -
-
+ + - + ${[0, 1, 2, 3, 4, 5].map( (i) => html` - - + - + Placeholder - - - + + + `, )} - -
-
- - - - - + + + + + + +

Section title

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut sapien eget nisl interdum porta vitae eu libero.

-
-
- - - - - + + + + + + + Placeholder - + Eyebrow text -

+

Suspendisse nibh turpis, posuere ac ultrices non

Integer fermentum sed libero ac accumsan.

- Suspendisse — Vel efficitur -
-
- - + + + +
    ${[0, 1, 2].map( (i) => html`
  • - - - - + + + Placeholder - - - + + + Eyebrow text -

    +

    Suspendisse nibh turpis, posuere ac ultrices non

    - Suspendisse — Vel efficitur -
    -
    -
    + + +
  • `, )}
-
-
-
-
-
+ + + + +