Skip to content

Commit efa0d55

Browse files
v2: fix last tests + nesting scopes (#314)
* fix: checklist tests * fix: nested style scopes * fix: radio group id * feat: prevent empty files from exection * fix: format * refactor: remove sig suffix from popover * fix: last popover error * feat: add tests to ci * feat: build tools and utils for ci * fix: biome ignore path * feat: exclude biome from formatting package.json * fix: support select all cross platform test * feat: cache playwright install * fix: flaky pagination tests
1 parent f91283b commit efa0d55

File tree

22 files changed

+1129
-1135
lines changed

22 files changed

+1129
-1135
lines changed

.github/workflows/ci.yml

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,52 @@ jobs:
2525
uses: actions/setup-node@v3
2626
with:
2727
node-version: ${{ matrix.node }}
28+
29+
- name: 📦 Setup pnpm
30+
uses: pnpm/action-setup@v4
31+
with:
32+
version: 9.14.4
33+
run_install: false
34+
35+
- name: 🗂️ Get pnpm store directory
36+
shell: bash
37+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
38+
39+
- name: 💾 Setup pnpm cache
40+
uses: actions/cache@v4
41+
with:
42+
path: ${{ env.STORE_PATH }}
43+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
44+
restore-keys: |
45+
${{ runner.os }}-pnpm-store-
2846
2947
- name: ✨ Install dependencies
48+
run: pnpm install
49+
50+
- name: 💾 Cache Playwright browsers
51+
uses: actions/cache@v4
52+
id: playwright-cache
53+
with:
54+
path: ~/.cache/ms-playwright
55+
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}
56+
restore-keys: |
57+
${{ runner.os }}-playwright-
58+
59+
- name: 🎭 Install Playwright browsers
60+
if: steps.playwright-cache.outputs.cache-hit != 'true'
61+
run: pnpm exec playwright install --with-deps chromium
62+
63+
- name: 🎭 Install Playwright system dependencies
64+
if: steps.playwright-cache.outputs.cache-hit == 'true'
65+
run: pnpm exec playwright install-deps chromium
66+
67+
- name: 🔨 Build underlying tools and utilities
3068
run: |
31-
npm install -g pnpm@8
32-
pnpm install
69+
pnpm --filter ./libs/tools build
70+
pnpm --filter ./libs/utils build
3371
3472
- name: ✅ Check code style
35-
run: pnpm check
73+
run: pnpm check
74+
75+
- name: 🧪 Run tests
76+
run: pnpm test

biome.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"libs/icons/src/icons",
1818
"libs/icons/src/page",
1919
"libs/components/virtual-qds-icons.d.ts",
20-
"libs/components/icons-runtime.ts",
20+
"libs/components/src/icons-runtime.ts",
2121
"libs/components/styles/tailwind/qds-tailwind.css",
2222
"libs/utils/lib/*",
2323
".vscode",
@@ -108,6 +108,12 @@
108108
}
109109
}
110110
},
111+
{
112+
"include": ["**/package.json"],
113+
"formatter": {
114+
"enabled": false
115+
}
116+
},
111117
{
112118
"include": [
113119
"apps/docs/src/root.tsx",

docs/src/routes/components/checklist/examples/hero.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default component$(() => {
1010
<Checklist.Root class="checklist-root">
1111
{items.map((item) => (
1212
<Checklist.Item class="checkbox-root" key={item}>
13-
<Checklist.ItemTrigger class="checkbox-trigger">
13+
<Checklist.ItemTrigger class="checkbox-trigger ui-checked:bg-red-500">
1414
<Checklist.ItemIndicator class="checkbox-indicator">
1515
<LuCheck />
1616
</Checklist.ItemIndicator>

docs/src/routes/components/checklist/examples/select-all.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,30 @@ export default component$(() => {
88

99
return (
1010
<Checklist.Root>
11-
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
12-
<Checklist.SelectAll class="checkbox-trigger select-all-trigger">
13-
<Checklist.SelectAllIndicator class="checkbox-indicator">
11+
<div>
12+
<Checklist.SelectAll class=" ui-checked:bg-red-500 ui-mixed:bg-yellow-500">
13+
<Checklist.SelectAllIndicator class="checkbox-indicator ui-checked:bg-red-500">
1414
<LuCheck data-check-icon />
1515
<LuMinus data-minus-icon />
1616
</Checklist.SelectAllIndicator>
1717
</Checklist.SelectAll>
18-
<Checklist.Label>All items</Checklist.Label>
1918
</div>
19+
<Checklist.Label>All items</Checklist.Label>
2020
<div style={{ marginLeft: "32px" }}>
2121
{items.map((item) => (
2222
<Checklist.Item
2323
style={{ marginBottom: "8px", marginTop: "8px" }}
2424
class="checkbox-root"
2525
key={item}
2626
>
27-
<Checklist.ItemTrigger class="checkbox-trigger">
28-
<Checklist.ItemIndicator class="checkbox-indicator">
29-
<LuCheck />
30-
</Checklist.ItemIndicator>
31-
</Checklist.ItemTrigger>
32-
<Checklist.ItemLabel>{item}</Checklist.ItemLabel>
27+
<span>
28+
<Checklist.ItemTrigger class="checkbox-trigger ui-checked:bg-red-500">
29+
<Checklist.ItemIndicator class="checkbox-indicator">
30+
<LuCheck />
31+
</Checklist.ItemIndicator>
32+
</Checklist.ItemTrigger>
33+
<Checklist.ItemLabel>{item}</Checklist.ItemLabel>
34+
</span>
3335
</Checklist.Item>
3436
))}
3537
</div>
Lines changed: 4 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,8 @@
11
import api from "./code-notate/api.json";
2+
import Hero from "./examples/hero.tsx";
3+
import SelectAll from "./examples/select-all.tsx";
24

35
# Checklist
46
A group of selectable items that can be toggled individually or all at once.
5-
<Showcase name="hero" />
6-
## Features
7-
<Features api={api} />
8-
## Anatomy
9-
<AnatomyTable api={api} />
10-
## Examples
11-
### Basic Usage
12-
The basic checklist setup allows users to select multiple items independently.
13-
<Showcase name="select-all" />
14-
This example demonstrates:
15-
- `Checklist.Root` as the container for all checklist items
16-
- `Checklist.Item` for individual selectable items
17-
- `Checklist.ItemTrigger` and `Checklist.ItemIndicator` for the checkbox interaction
18-
- `Checklist.ItemLabel` for item text
19-
- `Checklist.SelectAll` for the parent checkbox that controls all items
20-
- `Checklist.SelectAllIndicator` showing the mixed state with both check and minus icons
21-
- `Checklist.Label` for the select all text
22-
The select-all functionality automatically manages three states:
23-
- Unchecked when no items are selected
24-
- Mixed state when some items are selected
25-
- Checked when all items are selected
26-
Each item maintains its own state while staying synchronized with the select-all checkbox.
27-
Note: The example uses Lucide icons (`LuCheck` and `LuMinus`) to display the checkbox states, but you can customize these with your own icons or indicators.
28-
29-
### Form Integration
30-
Integrate with native HTML forms by adding the `Checklist.HiddenInput` component under each item.
31-
The selected items are then automatically submitted with the form. The `name` input is required and should be unique
32-
within the form.
33-
<Showcase name="form" />
34-
35-
## Component State
36-
### Using Component State
37-
The Checklist component provides a powerful way to manage multiple checkbox selections with a "select all" capability. Let's look at how to implement and control the checklist state.
38-
The basic checklist state is demonstrated in the hero example:
39-
<Showcase name="hero" />
40-
For more advanced state management, including a "select all" feature:
41-
<Showcase name="select-all" />
42-
This example demonstrates:
43-
- Individual item selection state
44-
- Select all functionality
45-
- Mixed state when only some items are selected
46-
### State Interactions
47-
The checklist maintains three main states:
48-
- Unchecked: No items selected
49-
- Mixed: Some items selected
50-
- Checked: All items selected
51-
The select-all checkbox automatically updates based on the state of individual items:
52-
- Shows unchecked when no items are selected
53-
- Shows a mixed state when some items are selected
54-
- Shows checked when all items are selected
55-
To respond to state changes, the checklist items and select-all checkbox are automatically synchronized:
56-
1. When clicking the select-all checkbox:
57-
- If unchecked or mixed: All items become checked
58-
- If checked: All items become unchecked
59-
2. When clicking individual items:
60-
- Updates the select-all checkbox state based on overall selection
61-
- Maintains the mixed state when appropriate
62-
The state management is handled automatically by the component, requiring no additional configuration from the user. Simply structure your checklist with the appropriate components and the state synchronization works out of the box.
63-
64-
Based on the provided implementation and examples, I'll document the configuration options for the Checklist component.
65-
## Core Configuration
66-
### Select All Behavior
67-
The Checklist component supports a "select all" functionality that manages the state of all child checkboxes. As shown in the `select-all` example above, this requires configuring both the select all trigger and individual items.
68-
The select all state automatically manages three possible values:
69-
- `false` - No items checked
70-
- `true` - All items checked
71-
- `"mixed"` - Some items checked
72-
### Item Management
73-
Items must be direct children of `Checklist.Root` to be properly tracked. The component internally manages indices for state synchronization.
74-
> Each `Checklist.Item` requires a unique key when mapping over items to maintain proper state tracking.
75-
### Group Configuration
76-
The Checklist is configured as a checkbox group by default with the following characteristics:
77-
```typescript
78-
type ChecklistContext = {
79-
isAllCheckedSig: Signal<boolean | "mixed">;
80-
checkedStatesSig: Signal<(boolean | "mixed")[]>;
81-
};
82-
```
83-
The context manages:
84-
- Overall checked state (`isAllCheckedSig`)
85-
- Individual item states (`checkedStatesSig`)
86-
### Form Integration
87-
The Checklist can be integrated with forms through the `HiddenInput` component. As shown in the `hero` example above, this manages the form submission state for all checkboxes in the group.
88-
## Advanced Configuration
89-
### State Synchronization
90-
The Checklist implements a bi-directional state synchronization:
91-
1. Select All → Items:
92-
- When select all is toggled, all items update to match
93-
- Mixed state is preserved when partially selected
94-
2. Items → Select All:
95-
- Select all updates based on collective item state
96-
- Automatically switches to mixed state when appropriate
97-
### Custom Layouts
98-
While the component handles state management, the layout is fully customizable. As shown in the `select-all` example above, items can be nested and grouped with custom spacing and hierarchies.
99-
> Note: The internal state tracking works regardless of DOM structure, but items must remain direct children of `Checklist.Root` in the component tree.
100-
101-
Based on the provided implementation and examples, I'll document the form-specific features of the Checklist component.
102-
## Forms
103-
The Checklist component provides form integration through the `<Checklist.HiddenInput>` component, which manages multiple checkbox form inputs.
104-
The component follows a group pattern where the select-all functionality can control multiple checkbox states simultaneously.
105-
<Showcase name="select-all" />
106-
In this example, the select-all checkbox controls the state of all child checkboxes, maintaining form state synchronization. When some items are selected, the select-all checkbox displays a mixed state, indicated by the minus icon.
107-
The form state is managed through the `ChecklistContext`, which tracks:
108-
- The overall checked state (`isAllCheckedSig`)
109-
- Individual item states (`checkedStatesSig`)
110-
The `<Checklist.Root>` component acts as a form group container with the appropriate `role="group"` attribute, ensuring proper form semantics and accessibility.
111-
Note: The current implementation doesn't show explicit form validation examples, but the component structure includes `<Checklist.Error>` for handling validation states when needed.
112-
113-
114-
115-
<APITable api={api} />
7+
<Hero />
8+
<SelectAll />

libs/components/src/checklist/checklist-item.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,10 @@ export const ChecklistItem = component$((props: PublicChecklistItemProps) => {
2626
});
2727

2828
const isCheckedSig = useSignal(false);
29-
const isSelectAllSig = useSignal(false);
3029

3130
useTask$(function checkAllManager({ track }) {
3231
track(() => context.isAllCheckedSig.value);
3332

34-
isSelectAllSig.value = true;
35-
3633
if (context.isAllCheckedSig.value === true) {
3734
isCheckedSig.value = true;
3835
} else if (context.isAllCheckedSig.value === false) {
@@ -43,8 +40,6 @@ export const ChecklistItem = component$((props: PublicChecklistItemProps) => {
4340
useTask$(function checkItemsManager({ track }) {
4441
track(() => isCheckedSig.value);
4542

46-
if (isSelectAllSig.value) return;
47-
4843
context.checkedStatesSig.value[index] = isCheckedSig.value;
4944

5045
if (context.checkedStatesSig.value.every((state) => state === true)) {

libs/components/src/checklist/checklist.browser.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,11 @@ test("main checkbox is unchecked when no items are checked", async () => {
132132
test("main checkbox is partially checked when some items are checked", async () => {
133133
render(<SelectAllExample />);
134134

135-
await expect.element(MainIndicator).toBeVisible();
136-
await expect.element(Indicators.nth(0)).toBeVisible();
135+
await expect.element(MainTrigger).toBeVisible();
136+
await expect.element(Indicators.nth(0)).not.toBeVisible();
137137

138138
// Click the first checkbox
139+
await expect.element(Triggers.nth(0)).toBeVisible();
139140
await userEvent.click(Triggers.nth(0));
140141

141142
await expect.element(MainTrigger).toHaveAttribute("aria-checked", "mixed");

libs/components/src/file-upload/file-upload.browser.tsx renamed to libs/components/src/file-upload/file-upload.old-tests.tsx

File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)