@shohojdhara/atomix 0.3.14 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/build-tools/EXAMPLES.md +372 -0
- package/build-tools/README.md +242 -0
- package/build-tools/__tests__/error-handler.test.js +230 -0
- package/build-tools/__tests__/index.test.js +141 -0
- package/build-tools/__tests__/rollup-plugin.test.js +194 -0
- package/build-tools/__tests__/utils.test.js +161 -0
- package/build-tools/__tests__/vite-plugin.test.js +129 -0
- package/build-tools/__tests__/webpack-loader.test.js +190 -0
- package/build-tools/error-handler.js +308 -0
- package/build-tools/index.d.ts +43 -0
- package/build-tools/index.js +88 -0
- package/build-tools/package.json +67 -0
- package/build-tools/rollup-plugin.js +236 -0
- package/build-tools/types.d.ts +163 -0
- package/build-tools/utils.js +203 -0
- package/build-tools/vite-plugin.js +161 -0
- package/build-tools/webpack-loader.js +123 -0
- package/dist/atomix.css +203 -90
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +3 -3
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/EXAMPLES.md +372 -0
- package/dist/build-tools/README.md +242 -0
- package/dist/build-tools/__tests__/error-handler.test.js +230 -0
- package/dist/build-tools/__tests__/index.test.js +141 -0
- package/dist/build-tools/__tests__/rollup-plugin.test.js +194 -0
- package/dist/build-tools/__tests__/utils.test.js +161 -0
- package/dist/build-tools/__tests__/vite-plugin.test.js +129 -0
- package/dist/build-tools/__tests__/webpack-loader.test.js +190 -0
- package/dist/build-tools/error-handler.js +308 -0
- package/dist/build-tools/index.d.ts +43 -0
- package/dist/build-tools/index.js +88 -0
- package/dist/build-tools/package.json +67 -0
- package/dist/build-tools/rollup-plugin.js +236 -0
- package/dist/build-tools/types.d.ts +163 -0
- package/dist/build-tools/utils.js +203 -0
- package/dist/build-tools/vite-plugin.js +161 -0
- package/dist/build-tools/webpack-loader.js +123 -0
- package/dist/charts.d.ts +1 -1
- package/dist/charts.js +86 -57
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +136 -112
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +2 -5
- package/dist/forms.js +140 -128
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +1 -1
- package/dist/heavy.js +136 -112
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +9 -61
- package/dist/index.esm.js +237 -286
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +250 -299
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +23 -8
- package/scripts/atomix-cli.js +170 -73
- package/scripts/cli/__tests__/README.md +81 -0
- package/scripts/cli/__tests__/basic.test.js +115 -0
- package/scripts/cli/__tests__/component-generator.test.js +332 -0
- package/scripts/cli/__tests__/integration.test.js +327 -0
- package/scripts/cli/__tests__/test-setup.js +133 -0
- package/scripts/cli/__tests__/token-manager.test.js +251 -0
- package/scripts/cli/__tests__/utils.test.js +161 -0
- package/scripts/cli/component-generator.js +253 -299
- package/scripts/cli/dependency-checker.js +355 -0
- package/scripts/cli/interactive-init.js +46 -5
- package/scripts/cli/template-manager.js +0 -2
- package/scripts/cli/templates/common-templates.js +636 -0
- package/scripts/cli/templates/composable-templates.js +148 -126
- package/scripts/cli/templates/index.js +23 -16
- package/scripts/cli/templates/project-templates.js +151 -23
- package/scripts/cli/templates/react-templates.js +280 -210
- package/scripts/cli/templates/scss-templates.js +90 -91
- package/scripts/cli/templates/testing-templates.js +206 -27
- package/scripts/cli/templates/testing-utils.js +278 -0
- package/scripts/cli/templates/types-templates.js +70 -56
- package/scripts/cli/theme-bridge.js +8 -2
- package/scripts/cli/token-manager.js +318 -206
- package/scripts/cli/utils.js +0 -1
- package/src/components/Accordion/Accordion.stories.tsx +369 -870
- package/src/components/AtomixGlass/AtomixGlass.tsx +80 -39
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +103 -81
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +8 -7
- package/src/components/AtomixGlass/glass-utils.ts +2 -2
- package/src/components/AtomixGlass/shader-utils.ts +5 -0
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +131 -0
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +2957 -2853
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +348 -0
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +103 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +50 -35
- package/src/components/AtomixGlass/stories/{ShaderVariants.stories.tsx → Shaders.stories.tsx} +1 -1
- package/src/components/AtomixGlass/stories/shared-components.tsx +90 -190
- package/src/components/Avatar/Avatar.stories.tsx +213 -1
- package/src/components/Badge/Badge.stories.tsx +121 -362
- package/src/components/Block/Block.stories.tsx +21 -12
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +141 -23
- package/src/components/Button/Button.stories.tsx +463 -1126
- package/src/components/Button/Button.test.tsx +107 -0
- package/src/components/Button/Button.tsx +46 -50
- package/src/components/Button/ButtonGroup.stories.tsx +373 -217
- package/src/components/Callout/Callout.stories.tsx +289 -634
- package/src/components/Card/Card.stories.tsx +248 -68
- package/src/components/Chart/Chart.stories.tsx +150 -8
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +151 -69
- package/src/components/Countdown/Countdown.stories.tsx +115 -8
- package/src/components/DataTable/DataTable.stories.tsx +346 -146
- package/src/components/DatePicker/DatePicker.stories.tsx +325 -1066
- package/src/components/Dropdown/Dropdown.stories.tsx +153 -33
- package/src/components/EdgePanel/EdgePanel.stories.tsx +230 -21
- package/src/components/Footer/Footer.stories.tsx +392 -328
- package/src/components/Form/Checkbox.stories.tsx +140 -6
- package/src/components/Form/Checkbox.test.tsx +63 -0
- package/src/components/Form/Checkbox.tsx +87 -51
- package/src/components/Form/Form.stories.tsx +119 -20
- package/src/components/Form/FormGroup.stories.tsx +127 -4
- package/src/components/Form/Radio.stories.tsx +140 -5
- package/src/components/Form/Select.stories.tsx +140 -8
- package/src/components/Form/Textarea.stories.tsx +149 -6
- package/src/components/Hero/Hero.stories.tsx +333 -32
- package/src/components/List/List.stories.tsx +141 -3
- package/src/components/Modal/Modal.stories.tsx +181 -42
- package/src/components/Popover/Popover.stories.tsx +448 -98
- package/src/components/Progress/Progress.stories.tsx +167 -5
- package/src/components/River/River.stories.tsx +1 -1
- package/src/components/SectionIntro/SectionIntro.stories.tsx +240 -48
- package/src/components/Spinner/Spinner.stories.tsx +102 -8
- package/src/components/Steps/Steps.stories.tsx +172 -43
- package/src/components/Tabs/Tabs.stories.tsx +136 -10
- package/src/components/Testimonial/Testimonial.stories.tsx +120 -3
- package/src/components/Todo/Todo.stories.tsx +198 -9
- package/src/components/Toggle/Toggle.stories.tsx +126 -39
- package/src/components/Tooltip/Tooltip.stories.tsx +194 -104
- package/src/components/Upload/Upload.stories.tsx +113 -24
- package/src/lib/README.md +2 -2
- package/src/lib/__tests__/theme-tools.test.ts +193 -0
- package/src/lib/composables/index.ts +2 -2
- package/src/lib/composables/useAtomixGlass.ts +28 -56
- package/src/lib/composables/useChartExport.ts +2 -7
- package/src/lib/composables/useDataTable.ts +46 -29
- package/src/lib/constants/components.ts +9 -32
- package/src/lib/theme/devtools/CLI.ts +1 -1
- package/src/lib/types/components.ts +1 -1
- package/src/lib/utils/__tests__/csv.test.ts +45 -0
- package/src/lib/utils/csv.ts +17 -0
- package/src/lib/utils/dataTableExport.ts +1 -10
- package/src/styles/01-settings/_index.scss +2 -1
- package/src/styles/01-settings/_settings.accordion.scss +28 -7
- package/src/styles/01-settings/_settings.colors.scss +11 -11
- package/src/styles/01-settings/_settings.typography.scss +5 -5
- package/src/styles/02-tools/_tools.utility-api.scss +14 -0
- package/src/styles/06-components/_components.accordion.scss +56 -14
- package/src/styles/06-components/_components.checkbox.scss +23 -17
- package/src/styles/99-utilities/_index.scss +2 -0
- package/src/styles/99-utilities/_utilities.scss +3 -1
- package/src/styles/99-utilities/_utilities.text-gradient.scss +45 -0
- package/themes/dark-complementary/README.md +98 -0
- package/themes/dark-complementary/index.scss +158 -0
- package/themes/default-light/README.md +81 -0
- package/themes/default-light/index.scss +154 -0
- package/themes/high-contrast/README.md +105 -0
- package/themes/high-contrast/index.scss +172 -0
- package/themes/test-theme/README.md +38 -0
- package/themes/test-theme/index.scss +47 -0
- package/scripts/cli/templates-original-backup.js +0 -1655
- package/scripts/cli/templates_backup.js +0 -684
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +0 -1438
- package/src/lib/composables/useButton.ts +0 -93
- package/src/lib/composables/useCheckbox.ts +0 -70
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
3
|
+
import { useState } from 'react';
|
|
2
4
|
import { Checkbox } from './Checkbox';
|
|
3
5
|
|
|
6
|
+
// Mock event handlers
|
|
7
|
+
const mockOnChange = fn();
|
|
8
|
+
|
|
4
9
|
const meta = {
|
|
5
10
|
title: 'Components/Form/Checkbox',
|
|
6
11
|
component: Checkbox,
|
|
@@ -8,8 +13,66 @@ const meta = {
|
|
|
8
13
|
layout: 'centered',
|
|
9
14
|
docs: {
|
|
10
15
|
description: {
|
|
11
|
-
component:
|
|
12
|
-
|
|
16
|
+
component: `
|
|
17
|
+
# Checkbox
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
Checkbox component allows users to select one or more options from a set. It supports checked, unchecked, and indeterminate states, and can be used in forms or as standalone controls. Checkboxes provide clear visual feedback and support keyboard navigation.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Checked, unchecked, and indeterminate states
|
|
26
|
+
- Label support
|
|
27
|
+
- Disabled state
|
|
28
|
+
- Validation states (valid/invalid)
|
|
29
|
+
- Glass morphism effect
|
|
30
|
+
- Accessible design
|
|
31
|
+
- Responsive behavior
|
|
32
|
+
|
|
33
|
+
## Accessibility
|
|
34
|
+
|
|
35
|
+
- Keyboard support: Navigate and toggle with keyboard
|
|
36
|
+
- Screen reader: State and label announced properly
|
|
37
|
+
- ARIA support: Proper roles and properties for checkbox components
|
|
38
|
+
- Focus management: Visible focus indicators maintained
|
|
39
|
+
|
|
40
|
+
## Usage Examples
|
|
41
|
+
|
|
42
|
+
### Basic Usage
|
|
43
|
+
|
|
44
|
+
\`\`\`tsx
|
|
45
|
+
<Checkbox
|
|
46
|
+
label="Option label"
|
|
47
|
+
checked={isChecked}
|
|
48
|
+
onChange={setChecked}
|
|
49
|
+
/>
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
### Indeterminate State
|
|
53
|
+
|
|
54
|
+
\`\`\`tsx
|
|
55
|
+
<Checkbox
|
|
56
|
+
label="Option label"
|
|
57
|
+
indeterminate={true}
|
|
58
|
+
/>
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
## API Reference
|
|
62
|
+
|
|
63
|
+
### Props
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Default | Description |
|
|
66
|
+
| ---- | ---- | ------- | ----------- |
|
|
67
|
+
| label | ReactNode | - | Checkbox label text or element |
|
|
68
|
+
| checked | boolean | false | Whether the checkbox is checked |
|
|
69
|
+
| disabled | boolean | false | Whether the checkbox is disabled |
|
|
70
|
+
| invalid | boolean | false | Whether the checkbox is invalid |
|
|
71
|
+
| valid | boolean | false | Whether the checkbox is valid |
|
|
72
|
+
| indeterminate | boolean | false | Whether the checkbox is in indeterminate state |
|
|
73
|
+
| glass | boolean \| AtomixGlassProps | false | Enable glass morphism effect |
|
|
74
|
+
| onChange | (event: ChangeEvent<HTMLInputElement>) => void | - | Callback when checkbox state changes |
|
|
75
|
+
`,
|
|
13
76
|
},
|
|
14
77
|
},
|
|
15
78
|
},
|
|
@@ -17,31 +80,63 @@ const meta = {
|
|
|
17
80
|
argTypes: {
|
|
18
81
|
label: {
|
|
19
82
|
control: 'text',
|
|
20
|
-
description: 'Checkbox label text',
|
|
83
|
+
description: 'Checkbox label text or element',
|
|
84
|
+
table: {
|
|
85
|
+
type: { summary: 'ReactNode' },
|
|
86
|
+
defaultValue: { summary: '-' },
|
|
87
|
+
},
|
|
21
88
|
},
|
|
22
89
|
checked: {
|
|
23
90
|
control: 'boolean',
|
|
24
91
|
description: 'Whether the checkbox is checked',
|
|
92
|
+
table: {
|
|
93
|
+
type: { summary: 'boolean' },
|
|
94
|
+
defaultValue: { summary: 'false' },
|
|
95
|
+
},
|
|
25
96
|
},
|
|
26
97
|
disabled: {
|
|
27
98
|
control: 'boolean',
|
|
28
99
|
description: 'Whether the checkbox is disabled',
|
|
100
|
+
table: {
|
|
101
|
+
type: { summary: 'boolean' },
|
|
102
|
+
defaultValue: { summary: 'false' },
|
|
103
|
+
},
|
|
29
104
|
},
|
|
30
105
|
invalid: {
|
|
31
106
|
control: 'boolean',
|
|
32
107
|
description: 'Whether the checkbox is invalid',
|
|
108
|
+
table: {
|
|
109
|
+
type: { summary: 'boolean' },
|
|
110
|
+
defaultValue: { summary: 'false' },
|
|
111
|
+
},
|
|
33
112
|
},
|
|
34
113
|
valid: {
|
|
35
114
|
control: 'boolean',
|
|
36
115
|
description: 'Whether the checkbox is valid',
|
|
116
|
+
table: {
|
|
117
|
+
type: { summary: 'boolean' },
|
|
118
|
+
defaultValue: { summary: 'false' },
|
|
119
|
+
},
|
|
37
120
|
},
|
|
38
121
|
indeterminate: {
|
|
39
122
|
control: 'boolean',
|
|
40
123
|
description: 'Whether the checkbox is in indeterminate state',
|
|
124
|
+
table: {
|
|
125
|
+
type: { summary: 'boolean' },
|
|
126
|
+
defaultValue: { summary: 'false' },
|
|
127
|
+
},
|
|
41
128
|
},
|
|
42
129
|
glass: {
|
|
43
|
-
control: 'boolean',
|
|
130
|
+
control: { type: 'boolean' },
|
|
44
131
|
description: 'Enable glass morphism effect',
|
|
132
|
+
table: {
|
|
133
|
+
type: { summary: 'boolean | AtomixGlassProps' },
|
|
134
|
+
defaultValue: { summary: 'false' },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
onChange: {
|
|
138
|
+
action: 'changed',
|
|
139
|
+
description: 'Callback when checkbox state changes',
|
|
45
140
|
},
|
|
46
141
|
},
|
|
47
142
|
} satisfies Meta<typeof Checkbox>;
|
|
@@ -50,9 +145,17 @@ export default meta;
|
|
|
50
145
|
type Story = StoryObj<typeof meta>;
|
|
51
146
|
|
|
52
147
|
// Basic checkbox
|
|
53
|
-
export const
|
|
148
|
+
export const BasicUsage: Story = {
|
|
54
149
|
args: {
|
|
55
150
|
label: 'Accept terms and conditions',
|
|
151
|
+
onChange: mockOnChange,
|
|
152
|
+
},
|
|
153
|
+
parameters: {
|
|
154
|
+
docs: {
|
|
155
|
+
description: {
|
|
156
|
+
story: 'Basic checkbox with label.',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
56
159
|
},
|
|
57
160
|
};
|
|
58
161
|
|
|
@@ -61,11 +164,19 @@ export const Checked: Story = {
|
|
|
61
164
|
args: {
|
|
62
165
|
label: 'Accept terms and conditions',
|
|
63
166
|
checked: true,
|
|
167
|
+
onChange: mockOnChange,
|
|
168
|
+
},
|
|
169
|
+
parameters: {
|
|
170
|
+
docs: {
|
|
171
|
+
description: {
|
|
172
|
+
story: 'Checked checkbox state.',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
64
175
|
},
|
|
65
176
|
};
|
|
66
177
|
|
|
67
178
|
// Checkbox states
|
|
68
|
-
export const
|
|
179
|
+
export const AllStates: Story = {
|
|
69
180
|
render: (args: any) => (
|
|
70
181
|
<div className="u-flex u-flex-column u-gap-3">
|
|
71
182
|
<Checkbox label="Default checkbox" />
|
|
@@ -77,12 +188,27 @@ export const States: Story = {
|
|
|
77
188
|
<Checkbox label="Indeterminate checkbox" indeterminate />
|
|
78
189
|
</div>
|
|
79
190
|
),
|
|
191
|
+
parameters: {
|
|
192
|
+
docs: {
|
|
193
|
+
description: {
|
|
194
|
+
story: 'Checkbox in all available states.',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
80
198
|
};
|
|
81
199
|
|
|
82
200
|
// Without label
|
|
83
201
|
export const WithoutLabel: Story = {
|
|
84
202
|
args: {
|
|
85
203
|
'aria-label': 'Checkbox without visible label',
|
|
204
|
+
onChange: mockOnChange,
|
|
205
|
+
},
|
|
206
|
+
parameters: {
|
|
207
|
+
docs: {
|
|
208
|
+
description: {
|
|
209
|
+
story: 'Checkbox without visible label, using aria-label.',
|
|
210
|
+
},
|
|
211
|
+
},
|
|
86
212
|
},
|
|
87
213
|
};
|
|
88
214
|
|
|
@@ -91,6 +217,7 @@ export const Glass: Story = {
|
|
|
91
217
|
args: {
|
|
92
218
|
label: 'Glass Checkbox',
|
|
93
219
|
glass: true,
|
|
220
|
+
onChange: mockOnChange,
|
|
94
221
|
},
|
|
95
222
|
render: (args: any) => (
|
|
96
223
|
<div
|
|
@@ -107,6 +234,13 @@ export const Glass: Story = {
|
|
|
107
234
|
<Checkbox {...args} />
|
|
108
235
|
</div>
|
|
109
236
|
),
|
|
237
|
+
parameters: {
|
|
238
|
+
docs: {
|
|
239
|
+
description: {
|
|
240
|
+
story: 'Checkbox with glass morphism effect.',
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
110
244
|
};
|
|
111
245
|
|
|
112
246
|
// Glass with custom settings
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
4
|
+
import { Checkbox } from './Checkbox';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
expect.extend(toHaveNoViolations);
|
|
8
|
+
|
|
9
|
+
// Mock AtomixGlass
|
|
10
|
+
vi.mock('../AtomixGlass/AtomixGlass', () => ({
|
|
11
|
+
AtomixGlass: ({ children }: any) => <div>{children}</div>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('Checkbox Component', () => {
|
|
15
|
+
it('renders correctly with label', () => {
|
|
16
|
+
render(<Checkbox label="Accept Terms" />);
|
|
17
|
+
// In current implementation, if no ID is provided, htmlFor is undefined, so label is not associated.
|
|
18
|
+
// screen.getByLabelText might fail or might not find the input.
|
|
19
|
+
// Let's see.
|
|
20
|
+
expect(screen.getByText('Accept Terms')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('associates label with input when ID is provided', () => {
|
|
24
|
+
render(<Checkbox label="Subscribe" id="subscribe-check" />);
|
|
25
|
+
expect(screen.getByLabelText('Subscribe')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('associates label with input WITHOUT ID', () => {
|
|
29
|
+
// This tests my proposed improvement: wrapping input in label or auto-ID
|
|
30
|
+
render(<Checkbox label="No ID Checkbox" />);
|
|
31
|
+
// If not associated, this throws
|
|
32
|
+
expect(screen.getByLabelText('No ID Checkbox')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('handles checked state', () => {
|
|
36
|
+
const handleChange = vi.fn();
|
|
37
|
+
render(<Checkbox checked onChange={handleChange} label="Checked" id="checked-id" />);
|
|
38
|
+
const input = screen.getByLabelText('Checked');
|
|
39
|
+
expect(input).toBeChecked();
|
|
40
|
+
|
|
41
|
+
fireEvent.click(input);
|
|
42
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('forwards ref', () => {
|
|
46
|
+
const ref = React.createRef<HTMLInputElement>();
|
|
47
|
+
render(<Checkbox ref={ref} label="Ref Checkbox" />);
|
|
48
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('handles indeterminate state', () => {
|
|
52
|
+
// This might need manual DOM check as indeterminate is a property, not attribute
|
|
53
|
+
const { getByRole } = render(<Checkbox indeterminate label="Indeterminate" id="indet" />);
|
|
54
|
+
const input = getByRole('checkbox') as HTMLInputElement;
|
|
55
|
+
expect(input.indeterminate).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should have no accessibility violations', async () => {
|
|
59
|
+
const { container } = render(<Checkbox label="Accessible Checkbox" id="a11y-check" />);
|
|
60
|
+
const results = await axe(container);
|
|
61
|
+
expect(results).toHaveNoViolations();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import React, { memo } from 'react';
|
|
1
|
+
import React, { memo, forwardRef, useRef, useEffect, useImperativeHandle } from 'react';
|
|
2
2
|
import { CheckboxProps } from '../../lib/types/components';
|
|
3
|
-
import { useCheckbox } from '../../lib/composables/useCheckbox';
|
|
4
3
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const CHECKBOX_CLASSES = {
|
|
6
|
+
BASE: 'c-checkbox',
|
|
7
|
+
INVALID: 'is-error',
|
|
8
|
+
VALID: 'is-valid',
|
|
9
|
+
DISABLED: 'is-disabled',
|
|
10
|
+
MIXED: 'c-checkbox--mixed',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const Checkbox = React.memo(forwardRef<HTMLInputElement, CheckboxProps>(({
|
|
10
14
|
label,
|
|
11
|
-
checked
|
|
15
|
+
checked,
|
|
12
16
|
onChange,
|
|
13
17
|
className = '',
|
|
14
18
|
style,
|
|
@@ -24,50 +28,84 @@ export const Checkbox: React.FC<CheckboxProps> = memo(({
|
|
|
24
28
|
'aria-describedby': ariaDescribedBy,
|
|
25
29
|
onClick,
|
|
26
30
|
glass,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
...props
|
|
32
|
+
}, ref) => {
|
|
33
|
+
// Local ref to handle indeterminate state
|
|
34
|
+
const localRef = useRef<HTMLInputElement>(null);
|
|
35
|
+
|
|
36
|
+
// Merge refs
|
|
37
|
+
useImperativeHandle(ref, () => localRef.current as HTMLInputElement);
|
|
38
|
+
|
|
39
|
+
// Handle indeterminate
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (localRef.current) {
|
|
42
|
+
localRef.current.indeterminate = Boolean(indeterminate);
|
|
43
|
+
}
|
|
44
|
+
}, [indeterminate]);
|
|
45
|
+
|
|
46
|
+
// Generate classes
|
|
47
|
+
let validationClass = '';
|
|
48
|
+
if (invalid) {
|
|
49
|
+
validationClass = CHECKBOX_CLASSES.INVALID;
|
|
50
|
+
} else if (valid) {
|
|
51
|
+
validationClass = CHECKBOX_CLASSES.VALID;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const disabledClass = disabled ? CHECKBOX_CLASSES.DISABLED : '';
|
|
55
|
+
const indeterminateClass = indeterminate ? CHECKBOX_CLASSES.MIXED : '';
|
|
56
|
+
const glassClass = glass ? 'c-checkbox--glass' : '';
|
|
57
|
+
|
|
58
|
+
const checkboxClass = `${CHECKBOX_CLASSES.BASE} ${validationClass} ${disabledClass} ${indeterminateClass} ${glassClass} ${className}`.trim();
|
|
59
|
+
|
|
60
|
+
const inputElement = (
|
|
61
|
+
<input
|
|
62
|
+
ref={localRef}
|
|
63
|
+
type="checkbox"
|
|
64
|
+
className="c-checkbox__input"
|
|
65
|
+
checked={checked}
|
|
66
|
+
onChange={onChange}
|
|
67
|
+
onClick={onClick}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
required={required}
|
|
70
|
+
id={id}
|
|
71
|
+
name={name}
|
|
72
|
+
value={value}
|
|
73
|
+
aria-label={!label ? ariaLabel : undefined}
|
|
74
|
+
aria-describedby={ariaDescribedBy}
|
|
75
|
+
aria-invalid={invalid}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
34
79
|
|
|
35
|
-
|
|
36
|
-
className: `${className} ${glass ? 'c-checkbox--glass' : ''}`.trim(),
|
|
37
|
-
disabled,
|
|
38
|
-
invalid,
|
|
39
|
-
valid,
|
|
40
|
-
indeterminate,
|
|
41
|
-
});
|
|
80
|
+
let content: React.ReactNode;
|
|
42
81
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
type="checkbox"
|
|
48
|
-
className="c-checkbox__input"
|
|
49
|
-
checked={checked}
|
|
50
|
-
onChange={onChange}
|
|
51
|
-
onClick={onClick}
|
|
52
|
-
disabled={disabled}
|
|
53
|
-
required={required}
|
|
54
|
-
id={id}
|
|
55
|
-
name={name}
|
|
56
|
-
value={value}
|
|
57
|
-
aria-label={!label ? ariaLabel : undefined}
|
|
58
|
-
aria-describedby={ariaDescribedBy}
|
|
59
|
-
aria-invalid={invalid}
|
|
60
|
-
/>
|
|
61
|
-
{label && (
|
|
82
|
+
if (id && label) {
|
|
83
|
+
content = (
|
|
84
|
+
<div className={checkboxClass} style={style}>
|
|
85
|
+
{inputElement}
|
|
62
86
|
<label className="c-checkbox__label" htmlFor={id}>
|
|
63
87
|
{label}
|
|
64
88
|
</label>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
} else if (label) {
|
|
92
|
+
// Wrap input in label for accessibility when no ID is provided
|
|
93
|
+
content = (
|
|
94
|
+
<label className={checkboxClass} style={style}>
|
|
95
|
+
{inputElement}
|
|
96
|
+
<span className="c-checkbox__label">{label}</span>
|
|
97
|
+
</label>
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
// No label
|
|
101
|
+
content = (
|
|
102
|
+
<div className={checkboxClass} style={style}>
|
|
103
|
+
{inputElement}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
68
107
|
|
|
69
108
|
if (glass) {
|
|
70
|
-
// Default glass settings for checkboxes
|
|
71
109
|
const defaultGlassProps = {
|
|
72
110
|
displacementScale: 40,
|
|
73
111
|
blurAmount: 1,
|
|
@@ -76,17 +114,15 @@ export const Checkbox: React.FC<CheckboxProps> = memo(({
|
|
|
76
114
|
cornerRadius: 6,
|
|
77
115
|
mode: 'shader' as const,
|
|
78
116
|
};
|
|
79
|
-
|
|
80
117
|
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
81
|
-
|
|
82
|
-
return <AtomixGlass {...glassProps}>{checkboxContent}</AtomixGlass>;
|
|
118
|
+
return <AtomixGlass {...glassProps}>{content}</AtomixGlass>;
|
|
83
119
|
}
|
|
84
120
|
|
|
85
|
-
return
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
export type { CheckboxProps };
|
|
121
|
+
return content;
|
|
122
|
+
}));
|
|
89
123
|
|
|
90
124
|
Checkbox.displayName = 'Checkbox';
|
|
91
125
|
|
|
126
|
+
export type { CheckboxProps };
|
|
127
|
+
|
|
92
128
|
export default Checkbox;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
2
3
|
import { useState } from 'react';
|
|
3
4
|
import { Checkbox } from './Checkbox';
|
|
4
5
|
import { Form } from './Form';
|
|
@@ -20,8 +21,69 @@ const meta = {
|
|
|
20
21
|
layout: 'centered',
|
|
21
22
|
docs: {
|
|
22
23
|
description: {
|
|
23
|
-
component:
|
|
24
|
-
|
|
24
|
+
component: `
|
|
25
|
+
# Form
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
Form component provides a semantic HTML form wrapper with enhanced functionality. It supports form validation, submission handling, and can be disabled as a whole. Forms work seamlessly with FormGroup and all form input components to create complete, accessible form experiences.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Semantic HTML form wrapper
|
|
34
|
+
- Form validation support
|
|
35
|
+
- Submission handling
|
|
36
|
+
- Disabled state
|
|
37
|
+
- Auto-complete control
|
|
38
|
+
- Method selection (GET/POST)
|
|
39
|
+
- Accessible design
|
|
40
|
+
- Responsive behavior
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- Screen reader: Form structure and labels announced properly
|
|
45
|
+
- ARIA support: Proper roles and properties for form components
|
|
46
|
+
- Keyboard support: Navigate and submit forms with keyboard
|
|
47
|
+
- Focus management: Maintains focus on interactive elements
|
|
48
|
+
|
|
49
|
+
## Usage Examples
|
|
50
|
+
|
|
51
|
+
### Basic Usage
|
|
52
|
+
|
|
53
|
+
\`\`\`tsx
|
|
54
|
+
<Form onSubmit={handleSubmit}>
|
|
55
|
+
<FormGroup label="Name" htmlFor="name">
|
|
56
|
+
<Input id="name" placeholder="Enter your name" />
|
|
57
|
+
</FormGroup>
|
|
58
|
+
<button type="submit">Submit</button>
|
|
59
|
+
</Form>
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
### With Validation
|
|
63
|
+
|
|
64
|
+
\`\`\`tsx
|
|
65
|
+
<Form
|
|
66
|
+
onSubmit={handleSubmit}
|
|
67
|
+
noValidate={false}
|
|
68
|
+
autoComplete="on"
|
|
69
|
+
>
|
|
70
|
+
{/* Form fields */}
|
|
71
|
+
</Form>
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
### Props
|
|
77
|
+
|
|
78
|
+
| Prop | Type | Default | Description |
|
|
79
|
+
| ---- | ---- | ------- | ----------- |
|
|
80
|
+
| disabled | boolean | false | Whether the form is disabled |
|
|
81
|
+
| method | 'get' \\| 'post' | 'get' | Form submission method |
|
|
82
|
+
| noValidate | boolean | false | Whether to disable browser validation |
|
|
83
|
+
| autoComplete | string | 'on' | Form autocomplete setting |
|
|
84
|
+
| className | string | - | Additional CSS class names |
|
|
85
|
+
| onSubmit | (event: FormEvent) => void | - | Callback when form is submitted |
|
|
86
|
+
`,
|
|
25
87
|
},
|
|
26
88
|
},
|
|
27
89
|
},
|
|
@@ -30,23 +92,47 @@ const meta = {
|
|
|
30
92
|
disabled: {
|
|
31
93
|
control: 'boolean',
|
|
32
94
|
description: 'Whether the form is disabled',
|
|
95
|
+
table: {
|
|
96
|
+
type: { summary: 'boolean' },
|
|
97
|
+
defaultValue: { summary: false },
|
|
98
|
+
},
|
|
33
99
|
},
|
|
34
100
|
className: {
|
|
35
101
|
control: 'text',
|
|
36
102
|
description: 'Additional CSS class names',
|
|
103
|
+
table: {
|
|
104
|
+
type: { summary: 'string' },
|
|
105
|
+
defaultValue: { summary: '-' },
|
|
106
|
+
},
|
|
37
107
|
},
|
|
38
108
|
method: {
|
|
39
109
|
control: { type: 'select' },
|
|
40
110
|
options: ['get', 'post'],
|
|
41
111
|
description: 'Form submission method',
|
|
112
|
+
table: {
|
|
113
|
+
type: { summary: '"get" | "post"' },
|
|
114
|
+
defaultValue: { summary: 'get' },
|
|
115
|
+
},
|
|
42
116
|
},
|
|
43
117
|
noValidate: {
|
|
44
118
|
control: 'boolean',
|
|
45
119
|
description: 'Whether to disable browser validation',
|
|
120
|
+
table: {
|
|
121
|
+
type: { summary: 'boolean' },
|
|
122
|
+
defaultValue: { summary: false },
|
|
123
|
+
},
|
|
46
124
|
},
|
|
47
125
|
autoComplete: {
|
|
48
126
|
control: 'text',
|
|
49
127
|
description: 'Form autocomplete setting',
|
|
128
|
+
table: {
|
|
129
|
+
type: { summary: 'string' },
|
|
130
|
+
defaultValue: { summary: 'on' },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
onSubmit: {
|
|
134
|
+
action: 'submitted',
|
|
135
|
+
description: 'Callback when form is submitted',
|
|
50
136
|
},
|
|
51
137
|
},
|
|
52
138
|
} satisfies Meta<FormWithOptionalChildren>;
|
|
@@ -55,10 +141,10 @@ export default meta;
|
|
|
55
141
|
type Story = StoryObj<typeof meta>;
|
|
56
142
|
|
|
57
143
|
// Basic form
|
|
58
|
-
export const
|
|
144
|
+
export const BasicUsage: Story = {
|
|
59
145
|
args: { children: undefined },
|
|
60
146
|
render: args => (
|
|
61
|
-
<Form {...args}>
|
|
147
|
+
<Form {...args} onSubmit={fn()}>
|
|
62
148
|
<FormGroup label="Name" htmlFor="name">
|
|
63
149
|
<Input id="name" placeholder="Enter your name" />
|
|
64
150
|
</FormGroup>
|
|
@@ -70,6 +156,13 @@ export const Basic: Story = {
|
|
|
70
156
|
</button>
|
|
71
157
|
</Form>
|
|
72
158
|
),
|
|
159
|
+
parameters: {
|
|
160
|
+
docs: {
|
|
161
|
+
description: {
|
|
162
|
+
story: 'Basic form with name and email fields.',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
73
166
|
};
|
|
74
167
|
|
|
75
168
|
// Complete form with all input types
|
|
@@ -77,7 +170,7 @@ export const CompleteForm: Story = {
|
|
|
77
170
|
args: { children: undefined },
|
|
78
171
|
render: () => (
|
|
79
172
|
<div style={{ width: '500px' }}>
|
|
80
|
-
<Form>
|
|
173
|
+
<Form onSubmit={fn()}>
|
|
81
174
|
<h2 className="u-mb-4">Registration Form</h2>
|
|
82
175
|
|
|
83
176
|
<FormGroup label="Full Name" htmlFor="fullName" required>
|
|
@@ -109,42 +202,48 @@ export const CompleteForm: Story = {
|
|
|
109
202
|
id="country"
|
|
110
203
|
name="country"
|
|
111
204
|
options={[
|
|
205
|
+
{ value: '', label: 'Select a country' },
|
|
112
206
|
{ value: 'us', label: 'United States' },
|
|
113
207
|
{ value: 'ca', label: 'Canada' },
|
|
114
|
-
{ value: 'mx', label: 'Mexico' },
|
|
115
208
|
{ value: 'uk', label: 'United Kingdom' },
|
|
116
209
|
]}
|
|
117
|
-
placeholder="Select your country"
|
|
118
210
|
/>
|
|
119
211
|
</FormGroup>
|
|
120
212
|
|
|
121
|
-
<FormGroup label="
|
|
122
|
-
<Textarea
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
213
|
+
<FormGroup label="Bio" htmlFor="bio">
|
|
214
|
+
<Textarea
|
|
215
|
+
id="bio"
|
|
216
|
+
name="bio"
|
|
217
|
+
placeholder="Tell us about yourself"
|
|
218
|
+
rows={4}
|
|
219
|
+
/>
|
|
127
220
|
</FormGroup>
|
|
128
221
|
|
|
129
|
-
<FormGroup label="
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</div>
|
|
222
|
+
<FormGroup label="Subscribe to newsletter">
|
|
223
|
+
<Checkbox
|
|
224
|
+
name="newsletter"
|
|
225
|
+
label="Yes, I would like to receive updates"
|
|
226
|
+
/>
|
|
135
227
|
</FormGroup>
|
|
136
228
|
|
|
137
229
|
<div className="u-flex u-gap-3 u-mt-4">
|
|
138
230
|
<button type="submit" className="c-btn c-btn--primary">
|
|
139
231
|
Register
|
|
140
232
|
</button>
|
|
141
|
-
<button type="reset" className="c-btn c-btn--
|
|
233
|
+
<button type="reset" className="c-btn c-btn--secondary">
|
|
142
234
|
Reset
|
|
143
235
|
</button>
|
|
144
236
|
</div>
|
|
145
237
|
</Form>
|
|
146
238
|
</div>
|
|
147
239
|
),
|
|
240
|
+
parameters: {
|
|
241
|
+
docs: {
|
|
242
|
+
description: {
|
|
243
|
+
story: 'Complete registration form with various input types.',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
148
247
|
};
|
|
149
248
|
|
|
150
249
|
// Interactive form
|