handzon-core 0.6.1 → 0.7.0
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/package.json +3 -1
- package/src/collections.ts +5 -0
- package/src/components/ai/ChatButton.tsx +1 -1
- package/src/components/ai/ChatPanel.tsx +9 -4
- package/src/components/auth/UserMenu.tsx +17 -8
- package/src/components/home/ActiveFilterChips.tsx +88 -0
- package/src/components/home/FilterBar.tsx +126 -77
- package/src/components/home/Pagination.tsx +1 -3
- package/src/components/home/ResumeRail.tsx +3 -1
- package/src/components/home/SortBar.tsx +65 -0
- package/src/components/home/TutorialCard.astro +57 -8
- package/src/components/mdx/Checkpoint.tsx +7 -2
- package/src/components/ui/Dropdown.tsx +91 -0
- package/src/components/ui/MultiSelect.tsx +205 -0
- package/src/index.ts +22 -27
- package/src/layouts/BaseLayout.astro +24 -1
- package/src/lib/progress/remote.ts +8 -0
- package/src/lib/progress/useProgress.ts +8 -0
- package/src/pages/Home.astro +65 -129
- package/src/pages/paths.ts +2 -1
- package/src/server/auth/config.ts +2 -1
- package/src/server/auth/schema.ts +1 -8
- package/src/server/auth.ts +1 -5
- package/src/server/db/schema.ts +1 -1
- package/src/server/handlers/progress.ts +50 -27
- package/src/server/handlers/tutorialStats.ts +6 -3
- package/styles/components/dropdown.css +144 -0
- package/styles/components/filterbar.css +128 -0
- package/styles/components/multiselect.css +206 -0
- package/styles/components.css +3 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/* Multi-select dropdown (handzon-core/src/components/ui/MultiSelect.tsx).
|
|
2
|
+
*
|
|
3
|
+
* Wraps Radix Popover. Visual parity with the single-select Dropdown:
|
|
4
|
+
* trigger reuses .hz-dd-label + .hz-dd-trigger + .hz-dd-caret. The
|
|
5
|
+
* popover content (option list, in-popover search, count badges,
|
|
6
|
+
* footer Clear) is what's specific to multi-select.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* The MultiSelect trigger is a single <button> wrapping the label
|
|
10
|
+
* chip + the inner trigger body. Radix Popover.Trigger asChild needs
|
|
11
|
+
* a *single* focusable element, so we collapse the structure into one
|
|
12
|
+
* button and let CSS draw the chip + body as siblings. */
|
|
13
|
+
.hz-ms-trigger {
|
|
14
|
+
background: transparent;
|
|
15
|
+
border: 0;
|
|
16
|
+
padding: 0;
|
|
17
|
+
/* The button is also a `.hz-dd` element. That sibling class sets
|
|
18
|
+
* font-size: 0.78em + the mono family. Don't redeclare font-size /
|
|
19
|
+
* font-weight / line-height / letter-spacing here — at equal
|
|
20
|
+
* specificity, this stylesheet loads after dropdown.css and would
|
|
21
|
+
* win, resolving `inherit` to the body's 16px and making the
|
|
22
|
+
* trigger taller than the single-select Sort dropdown. We only
|
|
23
|
+
* need to undo the UA button defaults that .hz-dd doesn't already
|
|
24
|
+
* cover (font-family + color). */
|
|
25
|
+
font-family: inherit;
|
|
26
|
+
color: inherit;
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: stretch;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
.hz-ms-trigger-body {
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 0.45rem;
|
|
35
|
+
padding: 0.4rem 0.6rem;
|
|
36
|
+
min-height: 2.5rem;
|
|
37
|
+
background: transparent;
|
|
38
|
+
border: 1px solid var(--color-border);
|
|
39
|
+
color: var(--color-fg);
|
|
40
|
+
transition: border-color 0.12s ease, color 0.12s ease;
|
|
41
|
+
}
|
|
42
|
+
.hz-ms-trigger:hover .hz-ms-trigger-body,
|
|
43
|
+
.hz-ms-trigger:focus-visible .hz-ms-trigger-body,
|
|
44
|
+
.hz-ms-trigger[data-state="open"] .hz-ms-trigger-body {
|
|
45
|
+
border-color: var(--color-accent);
|
|
46
|
+
color: var(--color-accent);
|
|
47
|
+
}
|
|
48
|
+
.hz-ms-trigger:focus-visible { outline: none; }
|
|
49
|
+
.hz-ms-trigger[data-active] .hz-ms-trigger-body {
|
|
50
|
+
border-color: var(--color-accent);
|
|
51
|
+
color: var(--color-accent);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Icon sits inside the label chip on the LEFT segment, not inside
|
|
55
|
+
* the trigger body, so the chip reads "[icon] LEVEL" and the body
|
|
56
|
+
* carries only the current value. */
|
|
57
|
+
.hz-ms-label {
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 0.4rem;
|
|
61
|
+
/* Re-assert uppercase here because the inline-flex layout on the
|
|
62
|
+
* chip creates a nested span structure that loses the inheritance
|
|
63
|
+
* from .hz-dd-label in some rendering contexts. */
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
}
|
|
66
|
+
.hz-ms-label-icon {
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
color: var(--color-muted);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Inherit the trigger's font (already mono + 0.78em via .hz-dd).
|
|
73
|
+
* No font-size override here — it would compound with the parent
|
|
74
|
+
* (0.78em × 0.78em ≈ 0.6em) and render as tiny ~10px text, out of
|
|
75
|
+
* step with the single-select Dropdown's value chrome.
|
|
76
|
+
*
|
|
77
|
+
* No muted color for the empty "Any" state either — the value text
|
|
78
|
+
* should read the same as the Sort dropdown's "Default" so the
|
|
79
|
+
* three trigger bodies share one visual rhythm. */
|
|
80
|
+
.hz-ms-value {
|
|
81
|
+
font-family: inherit;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ---------- Popover content ---------- */
|
|
85
|
+
|
|
86
|
+
.hz-ms-content {
|
|
87
|
+
z-index: 100;
|
|
88
|
+
min-width: 16rem;
|
|
89
|
+
max-width: 22rem;
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
background: var(--color-bg);
|
|
93
|
+
border: 1px solid var(--color-border);
|
|
94
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
95
|
+
font-family: var(--font-mono);
|
|
96
|
+
font-size: 0.78em;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.hz-ms-search {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 0.5rem;
|
|
103
|
+
padding: 0.55rem 0.7rem;
|
|
104
|
+
border-bottom: 1px solid var(--color-border);
|
|
105
|
+
color: var(--color-muted);
|
|
106
|
+
}
|
|
107
|
+
.hz-ms-search input {
|
|
108
|
+
background: transparent;
|
|
109
|
+
border: 0;
|
|
110
|
+
color: var(--color-fg);
|
|
111
|
+
font: inherit;
|
|
112
|
+
outline: none;
|
|
113
|
+
width: 100%;
|
|
114
|
+
}
|
|
115
|
+
.hz-ms-search input::-webkit-search-cancel-button { display: none; }
|
|
116
|
+
|
|
117
|
+
.hz-ms-viewport {
|
|
118
|
+
max-height: 16rem;
|
|
119
|
+
overflow-y: auto;
|
|
120
|
+
padding: 0.25rem 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.hz-ms-empty {
|
|
124
|
+
padding: 0.75rem 0.85rem;
|
|
125
|
+
color: var(--color-muted);
|
|
126
|
+
text-align: center;
|
|
127
|
+
font-style: italic;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.hz-ms-option {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
gap: 0.55rem;
|
|
134
|
+
padding: 0.45rem 0.7rem;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
user-select: none;
|
|
137
|
+
outline: none;
|
|
138
|
+
color: var(--color-fg);
|
|
139
|
+
}
|
|
140
|
+
.hz-ms-option:hover,
|
|
141
|
+
.hz-ms-option:focus-visible,
|
|
142
|
+
.hz-ms-option[tabindex="0"]:focus {
|
|
143
|
+
background: var(--color-surface);
|
|
144
|
+
}
|
|
145
|
+
.hz-ms-option[data-checked] {
|
|
146
|
+
color: var(--color-accent);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.hz-ms-check {
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
width: 14px;
|
|
154
|
+
height: 14px;
|
|
155
|
+
border: 1px solid var(--color-border);
|
|
156
|
+
flex-shrink: 0;
|
|
157
|
+
color: var(--color-accent);
|
|
158
|
+
}
|
|
159
|
+
.hz-ms-option[data-checked] .hz-ms-check {
|
|
160
|
+
border-color: var(--color-accent);
|
|
161
|
+
background: color-mix(in oklab, var(--color-accent) 10%, transparent);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.hz-ms-option-label {
|
|
165
|
+
flex: 1;
|
|
166
|
+
text-transform: lowercase;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
text-overflow: ellipsis;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.hz-ms-count {
|
|
173
|
+
color: var(--color-muted);
|
|
174
|
+
font-size: 0.9em;
|
|
175
|
+
flex-shrink: 0;
|
|
176
|
+
}
|
|
177
|
+
.hz-ms-option[data-checked] .hz-ms-count { color: var(--color-accent); }
|
|
178
|
+
|
|
179
|
+
.hz-ms-footer {
|
|
180
|
+
padding: 0.45rem 0.55rem;
|
|
181
|
+
border-top: 1px solid var(--color-border);
|
|
182
|
+
display: flex;
|
|
183
|
+
justify-content: flex-end;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.hz-ms-clear {
|
|
187
|
+
display: inline-flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 0.35rem;
|
|
190
|
+
padding: 0.25rem 0.55rem;
|
|
191
|
+
background: transparent;
|
|
192
|
+
border: 1px solid var(--color-border);
|
|
193
|
+
color: var(--color-muted);
|
|
194
|
+
font: inherit;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
text-transform: uppercase;
|
|
197
|
+
letter-spacing: 0.06em;
|
|
198
|
+
font-size: 0.88em;
|
|
199
|
+
transition: color 0.12s ease, border-color 0.12s ease;
|
|
200
|
+
}
|
|
201
|
+
.hz-ms-clear:hover,
|
|
202
|
+
.hz-ms-clear:focus-visible {
|
|
203
|
+
color: var(--color-accent);
|
|
204
|
+
border-color: var(--color-accent);
|
|
205
|
+
outline: none;
|
|
206
|
+
}
|
package/styles/components.css
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
@import "./components/expressive-code.css";
|
|
10
10
|
@import "./components/a11y.css";
|
|
11
11
|
@import "./components/modal.css";
|
|
12
|
+
@import "./components/dropdown.css";
|
|
13
|
+
@import "./components/multiselect.css";
|
|
14
|
+
@import "./components/filterbar.css";
|
|
12
15
|
|
|
13
16
|
/* Site chrome */
|
|
14
17
|
@import "./components/progress.css";
|