Skip to content

Commit e4b7d65

Browse files
committed
feat: add Checkbox, Radio, and Switch components with their Tailwind plugins and types.
1 parent e4dd78c commit e4b7d65

9 files changed

Lines changed: 223 additions & 2 deletions

File tree

src/components/checkbox/index.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Material Design 3 Checkbox Component - Tailwind Plugin
3+
*/
4+
5+
import plugin from 'tailwindcss/plugin';
6+
7+
export const checkboxPlugin: ReturnType<typeof plugin> = plugin(function ({ addComponents }) {
8+
const checkboxes = {
9+
'.md-checkbox': {
10+
'@apply relative inline-flex items-center justify-center w-10 h-10 cursor-pointer rounded-md-full hover:bg-md-on-surface/10 transition-colors': {},
11+
12+
'input[type="checkbox"]': {
13+
'@apply appearance-none w-4.5 h-4.5 border-2 border-md-on-surface-variant rounded-md-xs bg-transparent outline-none transition-all duration-200': {},
14+
15+
'&:checked': {
16+
'@apply bg-md-primary border-md-primary': {},
17+
// Checkmark icon styling could be done with a background image or SVG,
18+
// but for a pure CSS/Tailwind solution, we can use a pseudo-element or expect an SVG sibling.
19+
// Here we'll use a mask or background-image approach if we want pure CSS,
20+
// or rely on the HTML structure having an SVG.
21+
// For simplicity in this plugin, we'll style the input itself.
22+
23+
'&:before': {
24+
content: '""',
25+
'@apply absolute inset-0 flex items-center justify-center text-md-on-primary': {},
26+
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E")`,
27+
backgroundSize: '100%',
28+
backgroundPosition: 'center',
29+
backgroundRepeat: 'no-repeat',
30+
}
31+
},
32+
33+
'&:disabled': {
34+
'@apply border-md-on-surface/38 cursor-not-allowed': {},
35+
'&:checked': {
36+
'@apply bg-md-on-surface/38 border-transparent': {},
37+
}
38+
},
39+
40+
'&:focus-visible': {
41+
'@apply ring-2 ring-md-primary ring-offset-2': {},
42+
},
43+
44+
// Error state
45+
'&.error': {
46+
'@apply border-md-error': {},
47+
'&:checked': {
48+
'@apply bg-md-error border-md-error': {},
49+
},
50+
'&:focus-visible': {
51+
'@apply ring-md-error': {},
52+
}
53+
}
54+
}
55+
}
56+
};
57+
58+
addComponents(checkboxes);
59+
});
60+
61+
export default checkboxPlugin;

src/components/checkbox/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface CheckboxOptions {
2+
checked?: boolean;
3+
disabled?: boolean;
4+
indeterminate?: boolean;
5+
error?: boolean;
6+
}

src/components/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ export { cardPlugin } from './card';
77
export { textFieldPlugin } from './text-field';
88
export { dialogPlugin } from './dialog';
99
export { snackbarPlugin } from './snackbar';
10+
export { checkboxPlugin } from './checkbox';
11+
export { radioPlugin } from './radio';
12+
export { switchPlugin } from './switch';

src/components/radio/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Material Design 3 Radio Component - Tailwind Plugin
3+
*/
4+
5+
import plugin from 'tailwindcss/plugin';
6+
7+
export const radioPlugin: ReturnType<typeof plugin> = plugin(function ({ addComponents }) {
8+
const radios = {
9+
'.md-radio': {
10+
'@apply relative inline-flex items-center justify-center w-10 h-10 cursor-pointer rounded-md-full hover:bg-md-on-surface/10 transition-colors': {},
11+
12+
'input[type="radio"]': {
13+
'@apply appearance-none w-5 h-5 border-2 border-md-on-surface-variant rounded-md-full bg-transparent outline-none transition-all duration-200': {},
14+
15+
'&:checked': {
16+
'@apply border-md-primary border-[6px]': {},
17+
},
18+
19+
'&:disabled': {
20+
'@apply border-md-on-surface/38 cursor-not-allowed': {},
21+
'&:checked': {
22+
'@apply border-md-on-surface/38': {},
23+
}
24+
},
25+
26+
'&:focus-visible': {
27+
'@apply ring-2 ring-md-primary ring-offset-2': {},
28+
},
29+
30+
// Error state
31+
'&.error': {
32+
'@apply border-md-error': {},
33+
'&:checked': {
34+
'@apply border-md-error': {},
35+
},
36+
'&:focus-visible': {
37+
'@apply ring-md-error': {},
38+
}
39+
}
40+
}
41+
}
42+
};
43+
44+
addComponents(radios);
45+
});
46+
47+
export default radioPlugin;

src/components/radio/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface RadioOptions {
2+
checked?: boolean;
3+
disabled?: boolean;
4+
error?: boolean;
5+
value: string;
6+
}

src/components/switch/index.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Material Design 3 Switch Component - Tailwind Plugin
3+
*/
4+
5+
import plugin from 'tailwindcss/plugin';
6+
7+
export const switchPlugin: ReturnType<typeof plugin> = plugin(function ({ addComponents }) {
8+
const switches = {
9+
'.md-switch': {
10+
'@apply relative inline-flex h-8 w-[52px] shrink-0 cursor-pointer rounded-md-full border-2 border-md-outline bg-md-surface-variant transition-colors duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-md-primary focus-visible:ring-offset-2': {},
11+
12+
'&[aria-checked="true"]': {
13+
'@apply bg-md-primary border-md-primary': {},
14+
15+
// Thumb (checked)
16+
'.md-switch-thumb': {
17+
'@apply translate-x-5 bg-md-on-primary w-6 h-6': {},
18+
// Check icon (optional, typically handled by SVG inside thumb)
19+
'&:has(svg)': {
20+
'@apply w-6 h-6': {},
21+
}
22+
},
23+
},
24+
25+
'&[aria-checked="false"]': {
26+
// Thumb (unchecked)
27+
'.md-switch-thumb': {
28+
'@apply translate-x-0 bg-md-outline w-4 h-4 mt-1 ml-1': {},
29+
// Icon inside (unchecked)
30+
'svg': {
31+
'@apply hidden': {}, // Usually hidden or smaller in unchecked state M3
32+
}
33+
},
34+
'&:hover .md-switch-thumb': {
35+
'@apply bg-md-on-surface-variant': {},
36+
}
37+
},
38+
39+
'&:disabled': {
40+
'@apply cursor-not-allowed opacity-38 border-none bg-md-on-surface/12': {},
41+
'&[aria-checked="true"]': {
42+
'@apply bg-md-on-surface/12': {},
43+
'.md-switch-thumb': {
44+
'@apply bg-md-surface': {},
45+
}
46+
},
47+
'&[aria-checked="false"]': {
48+
'.md-switch-thumb': {
49+
'@apply bg-md-on-surface/38': {},
50+
}
51+
}
52+
},
53+
54+
// Thumb common
55+
'.md-switch-thumb': {
56+
'@apply pointer-events-none inline-block transform rounded-md-full shadow ring-0 transition duration-200 ease-in-out flex items-center justify-center': {},
57+
},
58+
59+
// Hidden input for form submission
60+
'input[type="checkbox"]': {
61+
'@apply sr-only': {},
62+
}
63+
}
64+
};
65+
66+
addComponents(switches);
67+
});
68+
69+
export default switchPlugin;

src/components/switch/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface SwitchOptions {
2+
checked?: boolean;
3+
disabled?: boolean;
4+
withIcon?: boolean;
5+
}

src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ export * from './theme';
1313
export { createMaterialConfig, createMaterialConfig as default } from './tailwind.config';
1414

1515
// Export component plugins
16-
export { buttonPlugin, cardPlugin, textFieldPlugin, dialogPlugin, snackbarPlugin } from './components';
16+
export {
17+
buttonPlugin,
18+
cardPlugin,
19+
textFieldPlugin,
20+
dialogPlugin,
21+
snackbarPlugin,
22+
checkboxPlugin,
23+
radioPlugin,
24+
switchPlugin
25+
} from './components';
1726
export * from './components/button/types';
1827
export * from './components/button/styles';
1928
export * from './components/card/types';
@@ -22,3 +31,6 @@ export * from './components/text-field/types';
2231
export * from './components/text-field/styles';
2332
export * from './components/dialog/types';
2433
export * from './components/snackbar/types';
34+
export * from './components/checkbox/types';
35+
export * from './components/radio/types';
36+
export * from './components/switch/types';

src/tailwind.config.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ import { defaultShapeTokens } from './tokens/shape';
55
import { easingTokens, durationTokens } from './tokens/motion';
66

77
import lightTheme from './theme/light';
8-
import { buttonPlugin, cardPlugin, textFieldPlugin, dialogPlugin, snackbarPlugin } from './components';
8+
import {
9+
buttonPlugin,
10+
cardPlugin,
11+
textFieldPlugin,
12+
dialogPlugin,
13+
snackbarPlugin,
14+
checkboxPlugin,
15+
radioPlugin,
16+
switchPlugin
17+
} from './components';
918

1019
/**
1120
* Material Design 3 Tailwind Configuration
@@ -226,6 +235,9 @@ export function createMaterialConfig(userConfig: Partial<Config> = {}): Config {
226235
textFieldPlugin,
227236
dialogPlugin,
228237
snackbarPlugin,
238+
checkboxPlugin,
239+
radioPlugin,
240+
switchPlugin,
229241
],
230242
...userConfig,
231243
};

0 commit comments

Comments
 (0)