Skip to content

Commit ac5a477

Browse files
scoped styles (#307)
* feat: v2 scoped styles * refactor: remove sig suffix * refactor: simplify checkbox root attrs * feat: fixed checkbox and field behavior * feat: correct backpatch handling * feat: correct ssr unmount * feat: handles initial ssr render * feat: managed aria id * feat: isomorphic dom nodes * feat: one global observer for ssr unmount * feat: move mount task to error handler * feat: transform fn for the lib * refactor: vite plugin handling and add back qrl to ref fn * feat: handle dollar injection * feat: unit tests on qrl injection * refactor: remove unused aria hook * feat: correct id behavior in checkbox description and error * feat: aria invalid derived from describedByIds * feat: have use bindings ignore adding attributes in the dom * fix: filter out binds * feat: more passing field tests * test: field tests passing * refactor: better dx ergonomics for this edge case * feat: use pkg pr new version * feat: correct deps * feat: binds correctly overriden in render comp * fix: lint * fix: malformed css gen * fix: bugbot bug
1 parent f1c801c commit ac5a477

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2188
-854
lines changed

biome.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"libs/icons/src/page",
1919
"libs/components/virtual-qds-icons.d.ts",
2020
"libs/components/icons-runtime.ts",
21+
"libs/components/styles/tailwind/qds-tailwind.css",
2122
"libs/utils/lib/*",
2223
".vscode",
2324
"test-results"

docs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"vite": "^7",
4141
"vite-plugin-image-optimizer": "^1.1.8",
4242
"vite-tsconfig-paths": "^4.2.1",
43-
"@qwik.dev/router": "2.0.0-beta.11",
44-
"@qwik.dev/core": "2.0.0-beta.11"
43+
"@qwik.dev/router": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@d48c3d2",
44+
"@qwik.dev/core": "https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@d48c3d2"
4545
},
4646
"dependencies": {
4747
"@stackblitz/sdk": "^1.11.0",

docs/src/global.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@layer qds, theme, base, components, utilities;
1+
@import "@qds.dev/ui/tailwind";
22

33
@import "tailwindcss";
44

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +0,0 @@
1-
.checkbox-root {
2-
display: flex;
3-
align-items: center;
4-
gap: 8px;
5-
}
6-
7-
.checkbox-trigger {
8-
width: 30px;
9-
height: 30px;
10-
border-radius: 8px;
11-
position: relative;
12-
background: gray;
13-
}
14-
15-
.checkbox-trigger:focus-visible {
16-
outline: 1px solid white;
17-
}
18-
19-
.checkbox-trigger[data-disabled] {
20-
opacity: 0.5;
21-
}
22-
23-
.checkbox-indicator[data-mixed] {
24-
display: flex;
25-
justify-content: center;
26-
}
27-
28-
.checkbox-indicator[data-checked] {
29-
display: flex;
30-
justify-content: center;
31-
position: absolute;
32-
inset: 0;
33-
align-items: center;
34-
background: #0a4d70;
35-
border-radius: 8px;
36-
}
37-
38-
/* ** CSS for Checklist examples ** */
39-
40-
.checklist-root {
41-
display: flex;
42-
flex-direction: column;
43-
gap: 8px;
44-
}
45-
46-
.select-all-trigger [data-check-icon],
47-
.select-all-trigger [data-minus-icon] {
48-
display: none;
49-
}
50-
51-
.select-all-trigger[data-checked] [data-check-icon] {
52-
display: block;
53-
}
54-
55-
.select-all-trigger[data-mixed] [data-minus-icon] {
56-
display: block;
57-
}
58-
59-
.submit-button {
60-
margin: 8px 0;
61-
background-color: #626262;
62-
padding: 3px 12px;
63-
cursor: pointer;
64-
border-radius: 8px;
65-
}
Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
import { Checkbox } from "@qds.dev/ui";
2-
import { component$, useStyles$ } from "@qwik.dev/core";
2+
import { component$, useSignal, useStyles$ } from "@qwik.dev/core";
33

44
export default component$(() => {
55
useStyles$(styles);
66

7+
const isError = useSignal(true);
8+
9+
const isRendered = useSignal(true);
10+
const isChecked = useSignal<"mixed" | boolean>(false);
11+
712
return (
8-
<Checkbox.Root>
9-
<Checkbox.Trigger class="checkbox-trigger">
10-
<Checkbox.Indicator class="checkbox-indicator">
11-
<LuCheck />
12-
</Checkbox.Indicator>
13-
</Checkbox.Trigger>
14-
</Checkbox.Root>
13+
<>
14+
<Checkbox.Root bind:checked={isChecked}>
15+
<Checkbox.Trigger class="size-10 bg-yellow-500 ui-checked:bg-red-500">
16+
<Checkbox.Indicator class="checkbox-indicator">Checked</Checkbox.Indicator>
17+
</Checkbox.Trigger>
18+
<Checkbox.Description>Description</Checkbox.Description>
19+
{isError.value && <Checkbox.Error>Error</Checkbox.Error>}
20+
</Checkbox.Root>
21+
<button type="button" onClick$={() => (isError.value = !isError.value)}>
22+
Toggle Error
23+
</button>
24+
<button type="button" onClick$={() => (isChecked.value = "mixed")}>
25+
Make mixed
26+
</button>
27+
</>
1528
);
1629
});
1730

18-
import { LuCheck } from "@qwikest/icons/lucide";
19-
// example styles
2031
import styles from "./checkbox.css?inline";
Lines changed: 5 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -1,214 +1,11 @@
11
import api from "./code-notate/api.json";
2+
import Hero from "./examples/hero";
3+
import { Link } from "@qwik.dev/router";
4+
5+
{/* <Link href="/components/otp">Go to OTP</Link> */}
26

37
# Checkbox
48

59
A clickable input that lets users make binary choices or select multiple options.
610

7-
<Showcase name="hero" />
8-
9-
## Features
10-
11-
<Features api={api} />
12-
13-
## Anatomy
14-
15-
<AnatomyTable api={api} />
16-
17-
## Examples
18-
19-
### Basic Usage
20-
21-
The most basic checkbox implementation requires a `Checkbox.Root` component with a `Checkbox.Trigger` and `Checkbox.Indicator` nested inside.
22-
23-
<Showcase name="initial" />
24-
25-
The `checked` prop on `Checkbox.Root` sets the initial checked state. When checked, the `Checkbox.Indicator` becomes visible.
26-
27-
### Visual Features
28-
29-
#### Labels and Descriptions
30-
31-
Add descriptive text with `Checkbox.Label` and `Checkbox.Description` components. The `isDescription` prop on `Checkbox.Root` enables description support.
32-
33-
<Showcase name="description" />
34-
35-
#### Mixed State
36-
37-
Checkboxes support an indeterminate state using the `"mixed"` value. This is useful for parent/child checkbox relationships.
38-
39-
<Showcase name="mixed-initial" />
40-
41-
### Advanced Usage
42-
43-
#### Form Integration
44-
45-
For form submissions, add `Checkbox.HiddenInput` to create a native checkbox input. Use `name` and `value` props to control form data.
46-
47-
<Showcase name="form" />
48-
49-
The `required` prop enforces validation:
50-
51-
<Showcase name="validation" />
52-
53-
You can customize the submitted value using the `value` prop:
54-
55-
<Showcase name="value" />
56-
57-
#### Error Handling
58-
59-
Display error messages using the `Checkbox.Error` component. This automatically sets the appropriate ARIA attributes.
60-
61-
<Showcase name="validation" />
62-
63-
#### Mixed State Management
64-
65-
For complex selection scenarios, use the `bind:checked` prop to handle mixed states programmatically.
66-
67-
<Showcase name="mixed-reactive" />
68-
69-
The checkbox cycles through mixed → checked → unchecked states when clicked.
70-
71-
## Component State
72-
73-
### Using Component State
74-
75-
The checkbox component provides several ways to control its state:
76-
77-
1. **Initial State**
78-
As shown in the Initial example, you can set the initial checked state using the `checked` prop on `Checkbox.Root`:
79-
80-
```tsx
81-
<Checkbox.Root checked>
82-
<Checkbox.Trigger>
83-
<Checkbox.Indicator />
84-
</Checkbox.Trigger>
85-
</Checkbox.Root>
86-
```
87-
88-
2. **Controlled State**
89-
For controlled state management, use the `bind:checked` prop as demonstrated in the Reactive example. This allows you to bind the checkbox state to your application's state.
90-
3. **Indeterminate State**
91-
The checkbox supports a third "mixed" or indeterminate state, as shown in the Mixed Initial example. This is useful for representing partially selected states, like in a tree structure or bulk selection interface.
92-
93-
### State Interactions
94-
95-
1. **Change Events**
96-
As shown in the Change example, use the `onChange$` prop to handle state changes:
97-
98-
```tsx
99-
<Checkbox.Root
100-
onChange$={(checked) => {
101-
// Handle the new checked state
102-
}}
103-
>
104-
```
105-
106-
2. **Disabled State**
107-
The Disabled example demonstrates how to disable the checkbox using the `disabled` prop. When disabled:
108-
109-
- The checkbox cannot be interacted with
110-
- The visual state reflects the disabled status
111-
- All interactions are prevented
112-
113-
3. **Programmatic Control**
114-
As shown in the Programmatic example, you can programmatically control the checkbox state from outside the component:
115-
116-
```tsx
117-
<button
118-
onClick$={() => {
119-
// Update the bound checked state
120-
isChecked.value = true;
121-
}}
122-
>
123-
Check the checkbox
124-
</button>
125-
```
126-
127-
4. **Mixed State Transitions**
128-
When in a mixed state, clicking the checkbox will first transition to a checked state, then follow the normal checked/unchecked cycle on subsequent clicks, as demonstrated in the Mixed Reactive example.
129-
The checkbox maintains a predictable state transition flow:
130-
131-
- mixed → checked
132-
- checked → unchecked
133-
- unchecked → checked
134-
135-
## Core Configuration
136-
137-
### Initial State
138-
139-
The checkbox can be configured with an initial state using the `checked` prop on `Checkbox.Root`. As shown in the `initial` example above, this sets an uncontrolled initial value.
140-
141-
> The default value is `false` if not specified.
142-
143-
### Value Configuration
144-
145-
The checkbox supports three states:
146-
147-
- `false` (unchecked)
148-
- `true` (checked)
149-
- `"mixed"` (indeterminate)
150-
As shown in the `mixed-initial` example above, the indeterminate state can be set initially.
151-
152-
### Form Integration
153-
154-
The checkbox can be configured for form submission with these props:
155-
156-
- `name` - Form field name
157-
- `value` - Custom value for form submission (defaults to "on")
158-
- `required` - Makes the field required
159-
As shown in the `value` example above, you can customize the submitted value.
160-
161-
## Advanced Configuration
162-
163-
### State Management
164-
165-
The checkbox supports both controlled and uncontrolled state management:
166-
167-
```typescript
168-
// Uncontrolled
169-
<Checkbox.Root checked={true} />
170-
// Controlled
171-
<Checkbox.Root bind:checked={isCheckedSignal} />
172-
```
173-
174-
### Description Configuration
175-
176-
When using `Checkbox.Description`, you must set `isDescription` prop on `Checkbox.Root`:
177-
178-
```typescript
179-
<Checkbox.Root isDescription>
180-
<Checkbox.Description>...</Checkbox.Description>
181-
</Checkbox.Root>
182-
```
183-
184-
> Failing to set `isDescription` will trigger a console warning.
185-
186-
### Technical Constraints
187-
188-
1. The `bind:checked` signal must be of type `Signal<boolean | "mixed">`
189-
2. The `onChange$` handler receives the new state as its only argument
190-
3. When in `mixed` state, the first click will set the state to `true`
191-
These behaviors can be observed in the `mixed-reactive` example above.
192-
193-
## Forms
194-
195-
The Checkbox component provides form integration through the `<Checkbox.HiddenInput>` component, which renders a native checkbox input that's visually hidden but still accessible for form submission.
196-
197-
<Showcase name="form" />
198-
The following props can be used to configure the form behavior:
199-
- `name`: Sets the name of the form field
200-
- `value`: Sets a custom value for the checkbox (defaults to "on")
201-
- `required`: Makes the checkbox a required form field
202-
<Showcase name="value" />
203-
## Form Validation
204-
The Checkbox supports form validation through the `<Checkbox.Error>` component. When rendered, it automatically puts the checkbox in an invalid state and connects it with the error message via ARIA.
205-
<Showcase name="validation" />
206-
The error message is displayed when form validation fails, and the checkbox trigger receives the appropriate ARIA attributes:
207-
- `aria-invalid="true"`
208-
- `aria-describedby` pointing to the error message
209-
## Mixed State in Forms
210-
For complex form scenarios, the checkbox supports a mixed (indeterminate) state that can be used when a form field represents a partial selection.
211-
<Showcase name="form-mixed" />
212-
The mixed state is reflected in the hidden input's `indeterminate` property, ensuring proper form submission behavior.
213-
214-
<APITable api={api} />
11+
<Hero />

docs/src/routes/components/otp/index.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import api from "./code-notate/api.json";
2+
import { Link } from "@qwik.dev/router";
23

34
import Hero from "./examples/hero";
45

56
<Hero />
67

8+
<Link href="/components/checkbox">Go to Checkbox</Link>
9+
710
# One-Time Password Input
811
Securely collect and validate numeric codes with automatic field navigation and keyboard support.
912
<Hero />

docs/vite.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { qdsTransformPlugin } from "@qds.dev/tools/rolldown";
12
import { asChild, icons } from "@qds.dev/tools/vite";
23
import { qwikVite } from "@qwik.dev/core/optimizer";
34
import { qwikRouter } from "@qwik.dev/router/vite";
@@ -34,6 +35,8 @@ export default defineConfig(({ command, mode }): UserConfig => {
3435
plugins: [
3536
asChild(),
3637
icons(),
38+
// This plugin handles transforms for our lib author DX. We add it here so it runs in the docs dev mode as well.
39+
qdsTransformPlugin(),
3740
tailwindcss(),
3841
qwikRouter({
3942
mdx: {
@@ -79,9 +82,12 @@ export default defineConfig(({ command, mode }): UserConfig => {
7982
},
8083
resolve: {
8184
alias: {
85+
"@qds.dev/ui/tailwind": resolve(
86+
__dirname,
87+
"../libs/components/styles/tailwind/qds-tailwind.css"
88+
),
8289
"@qds.dev/ui": resolve(__dirname, "../libs/components/src"),
8390
"@qds.dev/utils": resolve(__dirname, "../libs/utils/src"),
84-
"@qds.dev/ui-icons": resolve(__dirname, "../libs/icons/src"),
8591
"@qds.dev/tools": resolve(__dirname, "../libs/tools/src"),
8692
"~": resolve(__dirname, "src")
8793
},

0 commit comments

Comments
 (0)