Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
989fd6c
chore: 🤖 add pkg vite-plugin-static-copy
punkbit Mar 16, 2026
f8480ed
chore: 🤖 use vite setup to copy css to distribution
punkbit Mar 16, 2026
6498f65
refactor: 💡 make static copy process common for cjs and esm
punkbit Mar 16, 2026
3a2a704
docs: 📝 about css modules
punkbit Mar 16, 2026
dc46845
style: 💄 add Button css module
punkbit Mar 16, 2026
3fc0b07
style: 💄 assign css module to component Button
punkbit Mar 16, 2026
9e7c16b
chore: 🤖 add class-variance-authority and clsx
punkbit Mar 16, 2026
e76c05e
style: 💄 migrate styled component to css modules
punkbit Mar 16, 2026
9cbbd0a
style: 💄 migrate styled component to css modules
punkbit Mar 16, 2026
1fa943e
refactor: 💡 token generator
punkbit Mar 16, 2026
bbeb9f4
chore: 🤖 storybook support for css modules
punkbit Mar 16, 2026
380fbaa
chore: 🤖 add button stories for each state, e.g. primary, secondary, etc
punkbit Mar 16, 2026
35f64da
chore: 🤖 add changeset
punkbit Mar 16, 2026
2e962fa
chore: 🤖 remove comment
punkbit Mar 16, 2026
6eb6ae7
chore: 🤖 update changeset
punkbit Mar 16, 2026
05c5bb5
chore: 🤖 declare css files as sideEffects
punkbit Mar 16, 2026
6968188
refactor: 💡 make class utility libraries, e.g. cva; actual dependencies
punkbit Mar 16, 2026
843502b
chore: 🤖 add TODO button
punkbit Mar 16, 2026
7281cdd
style: 💄 possible typo, e.g. make similar bg size as others
punkbit Mar 16, 2026
2c9be4f
refactor: 💡 role button is redundant per the ARIA in HTML spec, add a…
punkbit Mar 16, 2026
c43c714
chore: 🤖 format
punkbit Mar 16, 2026
7adf448
refactor: 💡 generate tokens, e.g. theme config shared file as json, r…
punkbit Mar 16, 2026
8ed0aba
chore: 🤖 add TODO regarding removal of styled components for the futu…
punkbit Mar 16, 2026
3c75867
chore: 🤖 add migration note to changeset
punkbit Mar 16, 2026
eada9e0
refactor: 💡 forward props (delegated)
punkbit Mar 16, 2026
2e2791c
fix: 🐛 checkout design tokens from main
punkbit Mar 16, 2026
78ba449
fix: 🐛 if a consumer passes className, it would overwrite the variant…
punkbit Mar 16, 2026
2898d94
fix: 🐛 avoid double colmns
punkbit Mar 16, 2026
6d31da6
chore: 🤖 regenerate tokens
punkbit Mar 16, 2026
6f2919e
fix: 🐛 replace missing by global focus token
punkbit Mar 16, 2026
d270807
fix: 🐛 focus rings restored for keyboard users
punkbit Mar 16, 2026
7d01418
refactor: 💡 follow BEM naming convention
punkbit Mar 16, 2026
9585f36
chore: 🤖 include BEM rule in llm conventions
punkbit Mar 16, 2026
d13f1a9
fix: 🐛 empty token
punkbit Mar 16, 2026
f6988dc
fix: 🐛 .button > span selector to explicit .button__label class
punkbit Mar 16, 2026
376aad8
chore: 🤖 format
punkbit Mar 16, 2026
6ab18dd
fix: 🐛 claude review, missing prefers-reduced-motion support for the …
punkbit Mar 17, 2026
e51051d
fix: 🐛 add line
punkbit Mar 17, 2026
ac9aaa9
refactor: 💡 remove sideEffects redundancy
punkbit Apr 7, 2026
e6f8aca
fix: 🐛 package.json typo
punkbit Apr 7, 2026
ae9b0b2
style: 💄 missing background repeat, as commented by claude PR
punkbit Apr 7, 2026
dc3dbf8
fix: 🐛 Changed both attributes to use || undefined:
punkbit Apr 7, 2026
e7996a2
chore: 🤖 initial playwright Docker setup
punkbit Mar 17, 2026
2378678
chore: 🤖 create docker ignore file
punkbit Mar 17, 2026
e932a72
chore: 🤖 update gitignore to filter out snapshots, e.g. darwin
punkbit Mar 17, 2026
5c9d9e6
chore: 🤖 create a docker compose to ease orchestration
punkbit Mar 17, 2026
4abb5c6
chore: 🤖 update playwright-docker script port to 8282
punkbit Mar 17, 2026
24aa37c
test: 💍 create button visual regression test covering all states
punkbit Mar 17, 2026
9922830
docs: 📝 visual regression playwright setup
punkbit Mar 17, 2026
a268d25
fix: 🐛 bash syntax error in the playwright-docker script
punkbit Mar 17, 2026
a55a0fc
docs: 📝 visual regression docker requirement
punkbit Mar 17, 2026
0ba4623
test: 💍 added the aria-busy assertion
punkbit Mar 17, 2026
a5a73e3
chore: 🤖 track visual regression snapshots
punkbit Mar 17, 2026
3aba761
test: 💍 preevnts shimmer animation from causing noin-deterministic sc…
punkbit Mar 17, 2026
474540a
chore: 🤖 removed docker stop block, and unused DOCKER_INSTANCE_NAME v…
punkbit Mar 17, 2026
22d7fb6
refactor: 💡 added role="group" to ButtonGroupWrapper (semantically co…
punkbit Mar 17, 2026
d36e29d
docs: 📝 amend typo (separatily/separately)
punkbit Mar 17, 2026
90e7ab1
test: 💍 throw if null
punkbit Mar 17, 2026
ee90e44
chore: 🤖 map source to docker
punkbit Mar 17, 2026
8e339dc
fix: 🐛 button attribute values
punkbit Mar 17, 2026
0b6aeb6
chore: 🤖 update snapshots
punkbit Mar 17, 2026
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
13 changes: 13 additions & 0 deletions .changeset/eight-colts-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@clickhouse/click-ui': minor
---

This library will now use CSS Modules for styling and because it's distributed unbundled, gives the consumer application full control over bundling and optimisations. You'll only include what you actually use, resulting in smaller bundle sizes and better performance!

**Migration:**

Your bundler must be configured to handle `.module.css` imports from `node_modules`. Most popular bundlers (Vite, webpack, Parcel, Rollup with appropriate plugins) support CSS Modules by default or with minimal configuration.

NOTE: We're currently migrating from Styled-Components to CSS Modules. Some components may still use Styled-Components during the transition period.

To learn more about CSS modules support, check our documentation [here](https://github.com/ClickHouse/click-ui?tab=readme-ov-file#css-modules)
23 changes: 23 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
node_modules

dist
.storybook/out
storybook-static

playwright-report
test-results

.git
.gitignore

.idea
.vscode
*.swp
*.swo

.DS_Store
Thumbs.db

*.log
*.md
!README.md
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ tmp/*
!.yarn/releases
!.yarn/plugins

**.ts-snapshots
tests/**/*-actual.png
tests/**/*-diff.png
tests/**/*-darwin.png
test-results
playwright-report

.install
.vscode
20 changes: 20 additions & 0 deletions .llm/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ export interface ComponentProps extends React.HTMLAttributes<HTMLElement> {
- Use transient props (prefixed with `$`) for styled-component internal state
- Use `data-*` attributes for styling hooks instead of generated class names

### CSS Modules (BEM Naming)

When using CSS Modules (migration in progress from styled-components):

- **Follow BEM naming convention**:
- `.button` - Block (component root)
- `.button__icon` - Element (child of block, use double underscore)
- `.button--primary` - Modifier (variant/state, use double dash)
- `.button--primary:hover` - State pseudo-classes

- **Example structure**:
```css
.button { /* base styles */ }
.button__icon { /* icon element */ }
.button--primary { /* primary variant */ }
.button--primary:focus-visible { /* keyboard focus state */ }
```
- Use CSS custom properties from theme tokens: `var(--click-button-basic-color-primary-background-default)`
- Always include `:focus-visible` styles for keyboard accessibility, never use `outline: none` without replacement

### Accessibility (Mandatory)

- Interactive elements need `role`, `aria-label`, `aria-describedby`
Expand Down
36 changes: 36 additions & 0 deletions .scripts/bash/playwright-docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash

COMMAND="${1:-test}"

PORT="8282"

if [[ "$COMMAND" = "update" ]]; then
docker compose \
-f docker-compose.playwright.yml \
run \
--rm update-snapshots
elif [[ "$COMMAND" = "ui" ]]; then
docker rm -f playwright-ui 2>/dev/null

echo "🎭 Starting Playwright UI at http://localhost:$PORT..."

(sleep 3 &&
open "http://localhost:$PORT" 2>/dev/null ||
xdg-open "http://localhost:$PORT" 2>/dev/null) &

docker compose \
-f docker-compose.playwright.yml \
run \
--rm \
--service-ports \
--name playwright-ui \
ui
elif [[ "$COMMAND" = "report" ]]; then
npx playwright show-report
else
docker compose \
-f docker-compose.playwright.yml \
run \
--rm \
test
fi
50 changes: 46 additions & 4 deletions .scripts/js/generate-tokens.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { register } from '@tokens-studio/sd-transforms';
import StyleDictionary from 'style-dictionary';
import config from '../../src/theme/theme.config.json' with { type: 'json' };

const themes = ['dark', 'light'];
const THEME_DATA_ATTRIBUTE = `data-${config.storageKey}`;

await register(StyleDictionary);

Expand All @@ -17,6 +19,38 @@ StyleDictionary.registerTransform({
},
});

StyleDictionary.registerTransform({
type: 'name',
name: 'name/cti/kebab',
transform: (token, options) => {
if (options.prefix && options.prefix.length) {
return [options.prefix].concat(token.path).join('-');
} else {
return token.path.join('-');
}
},
});

StyleDictionary.registerFormat({
name: 'css/themed-variables',
format: function ({ dictionary, file }) {
const themeName = file.destination.replace('tokens-', '').replace('.css', '');
const tokens = dictionary.allTokens
.map(token => {
const varName = token.path.join('-');
const cleanValue = String(token.value).replace(/;+$/, '');
return ` --${varName}: ${cleanValue};`;
})
.join('\n');

if (themeName === 'light') {
return `:root,\n[${THEME_DATA_ATTRIBUTE}="light"] {\n${tokens}\n}`;
} else {
return `[${THEME_DATA_ATTRIBUTE}="dark"] {\n${tokens}\n}`;
}
},
});

StyleDictionary.registerFormat({
name: 'typescript/es6-theme',
format: function ({ dictionary, file }) {
Expand Down Expand Up @@ -46,10 +80,7 @@ StyleDictionary.registerFormat({

for (const theme of themes) {
const sd = new StyleDictionary({
source: [
`./tokens/**/!(${themes.join('|')}).json`,
`./tokens/**/${theme}.json`,
],
source: [`./tokens/**/!(${themes.join('|')}).json`, `./tokens/**/${theme}.json`],
preprocessors: ['tokens-studio'],
platforms: {
ts: {
Expand All @@ -63,6 +94,17 @@ for (const theme of themes) {
},
],
},
css: {
transformGroup: 'tokens-studio',
transforms: ['name/cti/kebab'],
buildPath: 'src/theme/styles/',
files: [
{
destination: `tokens-${theme}.css`,
format: 'css/themed-variables',
},
],
},
},
});

Expand Down
7 changes: 4 additions & 3 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ const config: StorybookConfig = {
},

async viteFinal(config, { configType }) {
// Workaround for Storybook 10.0.7 bug where MDX files generate file:// imports
// See: https://github.com/storybookjs/storybook/issues (mdx-react-shim resolution)
config.plugins = config.plugins || [];
config.plugins = (config.plugins || []).filter((plugin) => {
const pluginName = plugin && typeof plugin === 'object' && 'name' in plugin ? plugin.name : null;
return pluginName !== 'css-external';
});
config.plugins.push({
name: 'fix-storybook-mdx-shim',
resolveId(source) {
Expand Down
79 changes: 39 additions & 40 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useState, useEffect, ReactNode } from 'react';
import type { Preview } from '@storybook/react-vite';
import { Decorator } from '@storybook/react-vite';
import { styled } from 'styled-components';
import { themes } from 'storybook/theming';
import { ClickUIProvider } from '@/providers';
import { useState, useEffect, ReactNode } from "react";
import type { Preview } from "@storybook/react-vite";
import { Decorator } from "@storybook/react-vite";
import { styled } from "styled-components";
import { themes } from "storybook/theming";
import { ClickUIProvider } from "../src/providers";

const ThemeBlock = styled.div<{ $left?: boolean; $bfill?: boolean }>(
({ $left, $bfill: fill, theme }) => `
position: absolute;
top: 0.5rem;
left: ${$left || fill ? 0 : '50vw'};
left: ${$left || fill ? 0 : "50vw"};
right: 0;
height: fit-content;
bottom: 0;
Expand All @@ -22,26 +22,28 @@ const ThemeBlock = styled.div<{ $left?: boolean; $bfill?: boolean }>(

export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: 'system',
name: "Theme",
description: "Global theme for components",
defaultValue: "system",
toolbar: {
icon: 'circlehollow',
icon: "circlehollow",
items: [
{ value: 'system', icon: 'browser', title: 'system' },
{ value: 'dark', icon: 'moon', title: 'dark' },
{ value: 'light', icon: 'sun', title: 'light' },
{ value: "system", icon: "browser", title: "system" },
{ value: "dark", icon: "moon", title: "dark" },
{ value: "light", icon: "sun", title: "light" },
],
showName: true,
},
},
};

const getSystemTheme = (): 'dark' | 'light' => {
if (typeof window !== 'undefined' && window.matchMedia) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const getSystemTheme = (): "dark" | "light" => {
if (typeof window !== "undefined" && window.matchMedia) {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
return 'dark';
return "dark";
};

interface ThemeWrapperProps {
Expand All @@ -50,27 +52,24 @@ interface ThemeWrapperProps {
}

const ThemeWrapper = ({ themeSelection, children }: ThemeWrapperProps) => {
const [systemTheme, setSystemTheme] = useState<'dark' | 'light'>(getSystemTheme);
const [systemTheme, setSystemTheme] = useState<"dark" | "light">(getSystemTheme);

// Listen for system theme changes
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = () => {
setSystemTheme(mediaQuery.matches ? 'dark' : 'light');
setSystemTheme(mediaQuery.matches ? "dark" : "light");
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);

// Resolve the actual theme: handle "system" and fallback for undefined/null
const theme =
themeSelection === 'system' || !themeSelection ? systemTheme : themeSelection;
themeSelection === "system" || !themeSelection ? systemTheme : themeSelection;

return (
<ClickUIProvider
theme={theme}
config={{ tooltip: { delayDuration: 0 } }}
>
<ClickUIProvider theme={theme} config={{ tooltip: { delayDuration: 0 } }}>
<ThemeBlock $left>{children}</ThemeBlock>
</ClickUIProvider>
);
Expand All @@ -91,22 +90,22 @@ const preview: Preview = {
parameters: {
options: {
storySort: {
method: 'alphabetical',
method: "alphabetical",
order: [
'Introduction',
'Buttons',
'Cards',
'Layout',
'Forms',
'Display',
'Sidebar',
'Typography',
'Colors',
['Title', 'Text', 'Link'],
"Introduction",
"Buttons",
"Cards",
"Layout",
"Forms",
"Display",
"Sidebar",
"Typography",
"Colors",
["Title", "Text", "Link"],
],
},
},
actions: { argTypesRegex: '^on[A-Z].*' },
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
Expand Down
13 changes: 13 additions & 0 deletions Dockerfile.playwright
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM mcr.microsoft.com/playwright:v1.58.2-noble

WORKDIR /app

RUN corepack enable && corepack prepare yarn@4.5.3 --activate

COPY package.json yarn.lock .yarnrc.yml ./

RUN yarn install

COPY . .

CMD ["yarn", "test:visual"]
Loading
Loading