Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .storybook/main.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { dirname, join } from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';

import svgr from 'vite-plugin-svgr';
Expand All @@ -7,7 +8,7 @@ import { twigPlugin } from '../twing/vite-plugin-twig.mjs';

const here = dirname(fileURLToPath(import.meta.url));

export default {
const storybookConfig = {
stories: ['../src/**/*.stories.js', '../src/**/*.mdx'],
staticDirs: ['../static', '../src/assets'],
addons: [
Expand All @@ -20,13 +21,13 @@ export default {
name: '@storybook/html-vite',
options: {},
},
managerHead: (head) => {
managerHead(head) {
const iconSuffix = process.env.NODE_ENV === 'development' ? '-dev' : '';
return `${head}
<link rel="icon" href="favicons/favicon${iconSuffix}.ico" />
<link rel="icon" href="favicons/icon${iconSuffix}.svg" type="image/svg+xml" />`;
},
viteFinal: async (config) => {
async viteFinal(config) {
config.plugins = [
...(config.plugins ?? []),
twigPlugin(),
Expand All @@ -53,3 +54,5 @@ export default {
return config;
},
};

export default storybookConfig;
14 changes: 9 additions & 5 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ SyntaxHighlighter.registerLanguage('twig', twig);

// Create viewports using widths defined in design tokens
const breakpointViewports = Object.fromEntries(
Object.keys(breakpoints).map((name) => [
Object.entries(breakpoints).map(([name, value]) => [
`breakpoint-${name}`,
{
name: `$${breakpoints[name].name}`,
name: `$${value.name}`,
styles: {
width: breakpoints[name].value,
width: value.value,
// Account for padding and border around viewport preview
height: 'calc(100% - 20px)',
},
Expand Down Expand Up @@ -70,12 +70,16 @@ export const parameters = {
transform(code, storyContext) {
try {
const storyFunction = storyContext.originalStoryFn;
if (!storyFunction) return code;
if (!storyFunction) {
return code;
}
const rendered = storyFunction(
storyContext.args || storyContext.initialArgs,
);
const input = globalThis.__twig_inputs__?.get(rendered);
if (!input) return code;
if (!input) {
return code;
}
return makeTwigInclude(input.path, input.args);
} catch {
return code;
Expand Down
2 changes: 1 addition & 1 deletion .storybook/theme-decorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const updateTheme = (element, theme) => {

const themes = [];
for (const className of element.classList) {
if (className.startsWith('t-') && className !== theme) {
if (className !== theme && className.startsWith('t-')) {
themes.push(className);
}
}
Expand Down
2 changes: 1 addition & 1 deletion .style-dictionary/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ StyleDictionary.registerTransform({
name: 'custom/name/i/kebab-rejoin-n',
type: 'name',
transform(token) {
return token.name.replaceAll(/-n-(\d)/g, '-n$1');
return token.name.replaceAll(/-n-(?<digit>\d)/gv, '-n$<digit>');
},
});

Expand Down
4 changes: 3 additions & 1 deletion .style-dictionary/config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
const config = {
source: ['src/tokens/**/*.+(js|json)'],
platforms: {
// Export all the tokens in a single SCSS file.
Expand Down Expand Up @@ -149,3 +149,5 @@ export default {
},
},
};

export default config;
12 changes: 6 additions & 6 deletions .stylelintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ module.exports = {
true,
{
ignoreShorthands: [
/^grid-template/i, // 1
/gap$/i, // 2
/^(?:margin|padding)-(?:block|inline)$/i, // 3
/^grid-template/iv, // 1
/gap$/iv, // 2
/^(?:margin|padding)-(?:block|inline)$/iv, // 3
],
},
],
Expand Down Expand Up @@ -66,9 +66,9 @@ module.exports = {
except: [
'clear', // 1
'float', // 1
/^border-.+-radius$/i, // 1
/^margin$/i, // 2
/^padding$/i, // 2
/^border-.+-radius$/iv, // 1
/^margin$/iv, // 2
/^padding$/iv, // 2
],
},
],
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ You can also run checks individually:

## Project Structure

```
```text
cloudfour.com-patterns
├── .github # GitHub workflows and templates
├── .storybook
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The package includes both the compiled code and the source files. So you can eit

You can add the pattern library package as a dependency to your JavaScript application using npm:

```
```sh
npm install @cloudfour/patterns
```

Expand Down
107 changes: 105 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import cloudFourConfig from '@cloudfour/eslint-config';
import * as mdx from 'eslint-plugin-mdx';

export default [
const config = [
{
ignores: [
'dist/',
Expand Down Expand Up @@ -90,7 +90,7 @@ export default [
files: ['**/*.mdx'],
rules: {
// The auto-fixer for this rule does not work with .mdx files.
'import/order': 'off',
'import-x/order': 'off',
// Our docs pages import Storybook's doc components and their own stories
// module, then reference them only inside JSX:
//
Expand All @@ -109,6 +109,107 @@ export default [
},
},

{
// `checkJs` is on, so JSDoc is how our .js files carry type information
// rather than a place we write prose. Blocks like
//
// /** @param {Args} args */
//
// exist to type a parameter, and demanding a description above each one
// produces filler rather than documentation. The rules that check the types
// themselves stay on.
files: ['**/*.{js,mjs,cjs}'],
rules: {
'jsdoc/require-description': 'off',
},
},

{
// Every snake_case identifier these files declare belongs to someone else's
// naming scheme, so renaming them would break something:
//
// - Stories and arg files pass variables into Twig templates, where
// snake_case is the convention and the names are the template's contract.
// - Design tokens become CSS custom properties and Sass variables via
// style-dictionary, so a token key is part of our published output.
// - Demo and mock data mirror the shape of an external API, e.g. WordPress's
// `paginate_links()`.
// - `scripts/build-js.mjs` sets terser options such as `join_vars`.
files: [
'**/*.stories.js',
'**/*-args.js',
'**/arg-types.js',
'src/tokens/**',
'**/demo/**',
'scripts/build-js.mjs',
],
rules: {
camelcase: 'off',
},
},

{
files: ['**/*.{ts,tsx,mts,cts}'],
rules: {
// This and xo's `non-nullable-type-assertion-style` want opposite things.
// That rule rewrites `closest('.js-sky-nav') as HTMLElement` into
// `closest('.js-sky-nav')!`, which this rule then rejects. The elements
// are guaranteed by the templates that render these components, and
// adding null guards would change what happens when one is missing --
// from throwing to silently doing nothing -- which is a behaviour
// decision rather than lint cleanup.
'@typescript-eslint/no-non-null-assertion': 'off',
// Ambient type packages -- `@vitest/browser/matchers`,
// `@testing-library/jest-dom/vitest`, `vite/client` -- are only loadable
// through a triple-slash reference. They export no value, so the `import`
// form the rule suggests does not pull their globals in.
'@typescript-eslint/triple-slash-reference': 'off',
},
},

{
files: ['**/*.browser.test.ts'],
rules: {
// Regexes handed to Vitest's browser locators, e.g.
// `getByRole('button', {name: /^reply$/i})`, are never executed as
// regexes. Vitest serialises them into a selector string, and its parser
// rejects the `v` flag outright:
//
// Error while parsing selector `button[name=/^reply$/iv]`
// - unexpected symbol "v"
//
// Adding the flag here turns passing tests into failing ones.
'require-unicode-regexp': 'off',
},
},

{
// Storybook's preview file and the Vitest setup files exist to run for
// their side effects: registering a syntax-highlighting language, pulling
// in matchers, loading global styles. There is nothing to assign.
files: ['.storybook/preview.js', 'vitest.setup.*.ts'],
rules: {
'import-x/no-unassigned-import': 'off',
'unicorn/no-top-level-side-effects': 'off',
},
},

{
files: ['package.json'],
rules: {
// The rule reads `preprocess` as a `pre` hook for a `process` script we
// do not have. It is its own script, named for what it does.
'package-json/no-orphan-script-hooks': 'off',
// Both of these describe what we publish, and getting either wrong breaks
// consumers quietly: an inaccurate `sideEffects` lets bundlers drop our
// styles, and the `.d.ts`/`.mjs` pairing the types rule objects to is
// decided by `scripts/build-types.mjs`. Worth doing, but as a deliberate
// look at packaging rather than inside an ESLint upgrade.
'package-json/prefer-side-effects-field': 'off',
'package-json/require-types-in-exports': 'off',
},
},

{
// Stories and docs pages import Storybook through package export subpaths
// (`@storybook/addon-docs/blocks`, `storybook/preview-api`). This rule cannot
Expand All @@ -120,3 +221,5 @@ export default [
},
},
];

export default config;
Loading