@usequeek/theme-kit 0.1.22 → 0.1.24

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +1 -1
  3. package/README.md +275 -26
  4. package/api/collections.ts +14 -1
  5. package/api/orders.ts +15 -1
  6. package/apps/follow-card.tsx +5 -0
  7. package/apps/subscribe-form.tsx +2 -2
  8. package/components/auth/auth-flow.tsx +12 -0
  9. package/components/cart/cart-panel-controller.tsx +13 -0
  10. package/components/checkout/default-checkout-shell.tsx +5 -0
  11. package/components/checkout/policy-links.tsx +4 -0
  12. package/components/image.tsx +10 -0
  13. package/components/language-menu.css +349 -0
  14. package/components/language-menu.tsx +371 -0
  15. package/components/language-switcher.tsx +40 -8
  16. package/components/powered-by-queek.tsx +4 -3
  17. package/components/product-media-frame.tsx +5 -0
  18. package/components/product-metafields.tsx +9 -0
  19. package/components/variant-picker.css +4 -7
  20. package/edit-mode.tsx +4 -3
  21. package/hooks/use-address-autocomplete.ts +9 -0
  22. package/hooks/use-auth-flow.tsx +7 -12
  23. package/hooks/use-auth.ts +10 -0
  24. package/hooks/use-cart.ts +12 -0
  25. package/hooks/use-categories.ts +16 -2
  26. package/hooks/use-href.ts +9 -4
  27. package/hooks/use-posts.ts +16 -8
  28. package/hooks/use-product-detail.ts +25 -11
  29. package/hooks/use-product-questions.ts +16 -3
  30. package/hooks/use-products.ts +44 -22
  31. package/hooks/use-related-products.ts +17 -5
  32. package/hooks/use-resolve-coordinates.ts +9 -0
  33. package/hooks/use-reviews.ts +16 -2
  34. package/hooks/use-search.ts +10 -0
  35. package/hooks/use-shop-cart.ts +8 -0
  36. package/hooks/use-shop.ts +19 -6
  37. package/hooks/use-variant-selection.ts +23 -19
  38. package/index.ts +10 -11
  39. package/locales/en.default.json +4 -0
  40. package/navigation.tsx +2 -0
  41. package/package.json +25 -4
  42. package/page-renderer.tsx +11 -0
  43. package/provider.tsx +29 -3
  44. package/sdk/client.ts +183 -36
  45. package/sdk/error-handler.ts +17 -9
  46. package/sdk/queek-client.ts +11 -1
  47. package/sdk/server-store-client.ts +2 -2
  48. package/shared-blocks/embed.tsx +7 -3
  49. package/shared-blocks/manifest.ts +1 -1
  50. package/shared-blocks/product-qa.tsx +1 -1
  51. package/stores/address-modal-store.ts +1 -0
  52. package/stores/auth-modal-store.ts +1 -0
  53. package/stores/cart-panel-store.ts +1 -0
  54. package/stores/cart-store.ts +12 -7
  55. package/stores/checkout-store.ts +4 -4
  56. package/stores/location-store.ts +5 -0
  57. package/stores/orders-sheet-store.ts +1 -0
  58. package/stores/product-modal-store.ts +1 -0
  59. package/stores/search-modal-store.ts +1 -0
  60. package/stores/user-store.ts +1 -1
  61. package/strings/pseudo-locale.ts +1 -1
  62. package/theme-context.tsx +11 -1
  63. package/theme-mount.tsx +14 -0
  64. package/utils/format.ts +10 -1
  65. package/utils/menu-link.ts +8 -0
  66. package/utils/menu-placement.ts +91 -0
  67. package/utils/to-error.ts +4 -0
  68. package/utils/vendor-host.ts +1 -2
  69. package/scripts/verify-package.ts +0 -102
  70. package/tests/apps.test.tsx +0 -33
  71. package/tests/auth-email-errors.test.tsx +0 -55
  72. package/tests/currency-contract.test.ts +0 -85
  73. package/tests/default-metaobject-page.test.tsx +0 -56
  74. package/tests/display-format.test.ts +0 -403
  75. package/tests/fixtures/external-theme.tsx +0 -85
  76. package/tests/image-loading.test.tsx +0 -31
  77. package/tests/language-switcher.test.tsx +0 -350
  78. package/tests/list-pagination.test.tsx +0 -71
  79. package/tests/locale-switch.test.ts +0 -197
  80. package/tests/locale-transport.test.tsx +0 -321
  81. package/tests/manifest-parity.test.ts +0 -54
  82. package/tests/markdown-render.test.ts +0 -46
  83. package/tests/metaobjects-block.test.tsx +0 -70
  84. package/tests/navigation.test.tsx +0 -30
  85. package/tests/orders-walk.test.ts +0 -48
  86. package/tests/page-renderer-signature.test.tsx +0 -42
  87. package/tests/powered-by-queek.test.tsx +0 -65
  88. package/tests/powered-by-url.test.ts +0 -49
  89. package/tests/product-images.test.ts +0 -242
  90. package/tests/product-metafields.test.tsx +0 -154
  91. package/tests/public-surface.test.ts +0 -35
  92. package/tests/shop-query.test.ts +0 -64
  93. package/tests/shopper-chrome-strings.test.tsx +0 -271
  94. package/tests/storefront-client-scope.test.ts +0 -43
  95. package/tests/theme-hook-placement.test.tsx +0 -179
  96. package/tests/theme-strings-client.test.tsx +0 -310
  97. package/tests/theme-strings-pseudo.test.tsx +0 -171
  98. package/tests/theme-strings-typed.test.tsx +0 -97
  99. package/tests/theme-strings.test.tsx +0 -401
  100. package/tests/theme-typed-keys.fixture.ts +0 -76
  101. package/tests/use-shop-prefetch.test.tsx +0 -87
@@ -0,0 +1,349 @@
1
+ /* ============================================================
2
+ LanguageSwitcher variant="menu" — a trigger and a list of language links:
3
+ a popover on wide screens, a bottom sheet below 640px. Prefix: qn-lang.
4
+
5
+ STYLING SURFACE. Every part has a class AND a data-part attribute:
6
+ root .qn-lang--menu [data-qn-language-switcher][data-variant="menu"]
7
+ trigger .qn-lang__trigger [data-part="trigger"]
8
+ label/code .qn-lang__trigger-label [data-part="trigger-label"]
9
+ .qn-lang__trigger-code [data-part="trigger-code"]
10
+ chevron .qn-lang__chevron [data-part="chevron"]
11
+ scrim .qn-lang__scrim [data-part="scrim"]
12
+ popover/sheet .qn-lang__popover [data-part="popover"]
13
+ grabber .qn-lang__handle [data-part="handle"]
14
+ group label .qn-lang__title [data-part="title"]
15
+ list .qn-lang__options [data-part="list"]
16
+ item .qn-lang__item [data-part="item"]
17
+ link .qn-lang__option [data-part="option"] (aria-current / data-current on the current one)
18
+ name/code .qn-lang__name / __code [data-part="name"] / [data-part="code"]
19
+ marker .qn-lang__mark [data-part="mark"]
20
+ State on the root: data-state="open|closed", data-side="bottom|top",
21
+ data-align="start|end", data-mode="popover|sheet" (while open),
22
+ data-count="<n>", data-many="true" (more than 10), data-show-code="true".
23
+
24
+ VARIABLES (--qn-lang-*): each is read as var(--qn-lang-x, var(--qn-token, fallback)),
25
+ and none is declared on the root, so set them on the root, an ancestor or :root.
26
+ surface ink muted border radius item-radius item-hover current-bg accent
27
+ focus shadow row-height sheet-row-height gap pad offset popover-width
28
+ many-width many-columns list-max sheet-radius scrim z duration ease
29
+ Logical properties only: it mirrors itself in right-to-left storefronts.
30
+ ============================================================ */
31
+
32
+ .qn-lang--menu {
33
+ position: relative;
34
+ display: inline-block;
35
+ max-inline-size: 100%;
36
+ }
37
+
38
+ /* ── trigger ───────────────────────────────────────────── */
39
+
40
+ .qn-lang__trigger {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: 8px;
44
+ min-inline-size: 44px;
45
+ min-block-size: 44px;
46
+ padding-inline: 12px;
47
+ border: 1px solid var(--qn-lang-border, var(--qn-border, color-mix(in srgb, currentColor 22%, transparent)));
48
+ border-radius: var(--qn-lang-radius, var(--qn-radius-sm, 6px));
49
+ background: transparent;
50
+ color: var(--qn-lang-ink, var(--qn-text, inherit));
51
+ font: inherit;
52
+ line-height: 1.25;
53
+ white-space: nowrap;
54
+ cursor: pointer;
55
+ }
56
+
57
+ .qn-lang__trigger:hover {
58
+ border-color: var(--qn-lang-ink, var(--qn-text, currentColor));
59
+ }
60
+
61
+ .qn-lang__trigger:focus-visible,
62
+ .qn-lang__option:focus-visible {
63
+ outline: 2px solid var(--qn-lang-focus, var(--qn-focus, currentColor));
64
+ outline-offset: 2px;
65
+ }
66
+
67
+ .qn-lang__trigger-code,
68
+ .qn-lang__code {
69
+ display: none;
70
+ color: var(--qn-lang-muted, var(--qn-text-muted, color-mix(in srgb, currentColor 60%, transparent)));
71
+ font-size: 0.8em;
72
+ letter-spacing: 0.04em;
73
+ }
74
+
75
+ .qn-lang--menu[data-show-code='true'] .qn-lang__trigger-code,
76
+ .qn-lang--menu[data-show-code='true'] .qn-lang__code {
77
+ display: inline;
78
+ }
79
+
80
+ .qn-lang__trigger .qn-lang__chevron {
81
+ position: static;
82
+ inline-size: 10px;
83
+ block-size: 10px;
84
+ flex: none;
85
+ fill: none;
86
+ stroke: currentColor;
87
+ stroke-width: 1.8;
88
+ }
89
+
90
+ /* ── popover ───────────────────────────────────────────── */
91
+
92
+ .qn-lang__popover {
93
+ position: absolute;
94
+ z-index: var(--qn-lang-z, 50);
95
+ display: flex;
96
+ flex-direction: column;
97
+ box-sizing: border-box;
98
+ inline-size: var(--qn-lang-popover-width, 240px);
99
+ max-inline-size: calc(100vw - 32px);
100
+ max-block-size: var(--qn-lang-avail, none);
101
+ padding: var(--qn-lang-pad, 4px);
102
+ overflow: hidden;
103
+ border: 1px solid var(--qn-lang-border, var(--qn-border, color-mix(in srgb, currentColor 16%, transparent)));
104
+ border-radius: var(--qn-lang-radius, var(--qn-radius-md, 8px));
105
+ background: var(--qn-lang-surface, var(--qn-surface, Canvas));
106
+ color: var(--qn-lang-ink, var(--qn-text, CanvasText));
107
+ box-shadow: var(--qn-lang-shadow, 0 4px 16px rgb(0 0 0 / 0.08));
108
+ font: inherit;
109
+ text-align: start;
110
+ translate: var(--qn-lang-shift, 0px) 0;
111
+ }
112
+
113
+ .qn-lang__popover[hidden],
114
+ .qn-lang__scrim[hidden] {
115
+ display: none;
116
+ }
117
+
118
+ .qn-lang--menu[data-side='bottom'] > .qn-lang__popover {
119
+ inset-block-start: calc(100% + var(--qn-lang-offset, 8px));
120
+ }
121
+
122
+ .qn-lang--menu[data-side='top'] > .qn-lang__popover {
123
+ inset-block-end: calc(100% + var(--qn-lang-offset, 8px));
124
+ }
125
+
126
+ .qn-lang--menu[data-align='end'] > .qn-lang__popover {
127
+ inset-inline-end: 0;
128
+ }
129
+
130
+ .qn-lang--menu[data-align='start'] > .qn-lang__popover {
131
+ inset-inline-start: 0;
132
+ }
133
+
134
+ .qn-lang--menu[data-many='true'] > .qn-lang__popover {
135
+ inline-size: var(--qn-lang-many-width, var(--qn-lang-popover-width, 240px));
136
+ }
137
+
138
+ .qn-lang__handle,
139
+ .qn-lang__scrim {
140
+ display: none;
141
+ }
142
+
143
+ .qn-lang__title {
144
+ padding: 8px 12px 6px;
145
+ color: var(--qn-lang-muted, var(--qn-text-muted, color-mix(in srgb, currentColor 60%, transparent)));
146
+ font-size: 0.75em;
147
+ font-weight: 600;
148
+ letter-spacing: 0.06em;
149
+ text-transform: uppercase;
150
+ }
151
+
152
+ /* ── list ──────────────────────────────────────────────── */
153
+
154
+ .qn-lang__options {
155
+ display: grid;
156
+ grid-template-columns: repeat(var(--qn-lang-many-columns, 1), minmax(0, 1fr));
157
+ gap: var(--qn-lang-gap, 0);
158
+ flex: 1 1 auto;
159
+ min-block-size: 0;
160
+ max-block-size: var(--qn-lang-list-max, calc(var(--qn-lang-row-height, 44px) * 8.5));
161
+ margin: 0;
162
+ padding: 0;
163
+ overflow-y: auto;
164
+ overscroll-behavior: contain;
165
+ list-style: none;
166
+ }
167
+
168
+ /* The extra columns only apply to long lists. */
169
+ .qn-lang--menu:not([data-many='true']) .qn-lang__options {
170
+ grid-template-columns: minmax(0, 1fr);
171
+ }
172
+
173
+ .qn-lang__item {
174
+ display: block;
175
+ margin: 0;
176
+ padding: 0;
177
+ }
178
+
179
+ .qn-lang__option {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 12px;
183
+ box-sizing: border-box;
184
+ min-block-size: var(--qn-lang-row-height, 44px);
185
+ padding-inline: 12px;
186
+ border-radius: var(--qn-lang-item-radius, var(--qn-radius-sm, 6px));
187
+ color: inherit;
188
+ font: inherit;
189
+ text-decoration: none;
190
+ cursor: pointer;
191
+ }
192
+
193
+ .qn-lang__option:hover {
194
+ background: var(--qn-lang-item-hover, color-mix(in srgb, currentColor 6%, transparent));
195
+ }
196
+
197
+ .qn-lang__option[aria-current='true'] {
198
+ background: var(--qn-lang-current-bg, transparent);
199
+ font-weight: 600;
200
+ }
201
+
202
+ .qn-lang__option:focus-visible {
203
+ outline-offset: -2px;
204
+ }
205
+
206
+ .qn-lang__name {
207
+ flex: 1 1 auto;
208
+ min-inline-size: 0;
209
+ overflow: hidden;
210
+ text-overflow: ellipsis;
211
+ white-space: nowrap;
212
+ }
213
+
214
+ .qn-lang__code {
215
+ flex: none;
216
+ }
217
+
218
+ .qn-lang__mark {
219
+ display: inline-flex;
220
+ inline-size: 16px;
221
+ block-size: 16px;
222
+ flex: none;
223
+ color: var(--qn-lang-accent, var(--qn-accent, currentColor));
224
+ visibility: hidden;
225
+ }
226
+
227
+ .qn-lang__option[aria-current='true'] .qn-lang__mark {
228
+ visibility: visible;
229
+ }
230
+
231
+ .qn-lang__mark svg {
232
+ inline-size: 100%;
233
+ block-size: 100%;
234
+ fill: none;
235
+ stroke: currentColor;
236
+ stroke-width: 1.8;
237
+ stroke-linecap: round;
238
+ stroke-linejoin: round;
239
+ }
240
+
241
+ /* ── motion ────────────────────────────────────────────── */
242
+
243
+ @keyframes qn-lang-pop-down {
244
+ from { opacity: 0; transform: translateY(-4px); }
245
+ to { opacity: 1; transform: none; }
246
+ }
247
+
248
+ @keyframes qn-lang-pop-up {
249
+ from { opacity: 0; transform: translateY(4px); }
250
+ to { opacity: 1; transform: none; }
251
+ }
252
+
253
+ @keyframes qn-lang-sheet-in {
254
+ from { transform: translateY(100%); }
255
+ to { transform: none; }
256
+ }
257
+
258
+ @keyframes qn-lang-fade-in {
259
+ from { opacity: 0; }
260
+ to { opacity: 1; }
261
+ }
262
+
263
+ .qn-lang--menu[data-side='bottom'] > .qn-lang__popover:not([hidden]) {
264
+ animation: qn-lang-pop-down var(--qn-lang-duration, 160ms) var(--qn-lang-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
265
+ }
266
+
267
+ .qn-lang--menu[data-side='top'] > .qn-lang__popover:not([hidden]) {
268
+ animation: qn-lang-pop-up var(--qn-lang-duration, 160ms) var(--qn-lang-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
269
+ }
270
+
271
+ @media (prefers-reduced-motion: no-preference) {
272
+ .qn-lang__chevron {
273
+ transition: rotate var(--qn-lang-duration, 160ms) var(--qn-lang-ease, ease);
274
+ }
275
+ }
276
+
277
+ .qn-lang--menu[data-state='open'] .qn-lang__trigger .qn-lang__chevron {
278
+ rotate: 180deg;
279
+ }
280
+
281
+ /* ── bottom sheet (narrow screens) ─────────────────────── */
282
+
283
+ @media (max-width: 639.98px) {
284
+ .qn-lang__scrim:not([hidden]) {
285
+ position: fixed;
286
+ inset: 0;
287
+ z-index: calc(var(--qn-lang-z, 50) - 1);
288
+ display: block;
289
+ background: var(--qn-lang-scrim, rgb(0 0 0 / 0.4));
290
+ animation: qn-lang-fade-in var(--qn-lang-duration, 160ms) linear;
291
+ }
292
+
293
+ /* [data-side][data-align] outranks the popover placement rules above. */
294
+ .qn-lang--menu[data-side][data-align] > .qn-lang__popover {
295
+ position: fixed;
296
+ inset-block: auto 0;
297
+ inset-inline: 0;
298
+ inline-size: auto;
299
+ max-inline-size: none;
300
+ max-block-size: 85vh;
301
+ max-block-size: 85dvh;
302
+ padding: var(--qn-lang-pad, 4px) var(--qn-lang-pad, 4px) calc(var(--qn-lang-pad, 4px) + env(safe-area-inset-bottom, 0px));
303
+ border-inline-width: 0;
304
+ border-block-end-width: 0;
305
+ border-start-start-radius: var(--qn-lang-sheet-radius, 16px);
306
+ border-start-end-radius: var(--qn-lang-sheet-radius, 16px);
307
+ border-end-start-radius: 0;
308
+ border-end-end-radius: 0;
309
+ translate: none;
310
+ }
311
+
312
+ .qn-lang--menu[data-side][data-align] > .qn-lang__popover:not([hidden]) {
313
+ animation: qn-lang-sheet-in calc(var(--qn-lang-duration, 160ms) * 1.5) var(--qn-lang-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
314
+ }
315
+
316
+ .qn-lang__handle {
317
+ display: block;
318
+ inline-size: 36px;
319
+ block-size: 4px;
320
+ margin: 8px auto 4px;
321
+ border-radius: 2px;
322
+ background: color-mix(in srgb, currentColor 25%, transparent);
323
+ }
324
+
325
+ .qn-lang--menu .qn-lang__options,
326
+ .qn-lang--menu:not([data-many='true']) .qn-lang__options {
327
+ grid-template-columns: minmax(0, 1fr);
328
+ max-block-size: none;
329
+ }
330
+
331
+ .qn-lang__option {
332
+ min-block-size: var(--qn-lang-sheet-row-height, 52px);
333
+ }
334
+ }
335
+
336
+ /* Page scroll is locked behind an open sheet. */
337
+ body.qn-lang-lock {
338
+ overflow: hidden;
339
+ }
340
+
341
+ @media (prefers-reduced-motion: reduce) {
342
+ .qn-lang--menu[data-side][data-align] > .qn-lang__popover:not([hidden]) {
343
+ animation: qn-lang-fade-in 80ms linear;
344
+ }
345
+
346
+ .qn-lang__scrim:not([hidden]) {
347
+ animation-duration: 80ms;
348
+ }
349
+ }