@xaroth.nl/design 0.1.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.
Files changed (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/all.css +2536 -0
  4. package/dist/astro.d.ts +41 -0
  5. package/dist/astro.js +41 -0
  6. package/dist/bem.d.ts +12 -0
  7. package/dist/bem.js +19 -0
  8. package/dist/components/alert/alert.astro +46 -0
  9. package/dist/components/alert/alert.css +68 -0
  10. package/dist/components/alert/alert.d.ts +11 -0
  11. package/dist/components/alert/alert.js +6 -0
  12. package/dist/components/alert/index.d.ts +14 -0
  13. package/dist/components/alert/index.js +27 -0
  14. package/dist/components/avatar/avatar.astro +23 -0
  15. package/dist/components/avatar/avatar.css +35 -0
  16. package/dist/components/avatar/avatar.d.ts +5 -0
  17. package/dist/components/avatar/avatar.js +7 -0
  18. package/dist/components/avatar/index.d.ts +32 -0
  19. package/dist/components/avatar/index.js +24 -0
  20. package/dist/components/badge/badge.astro +11 -0
  21. package/dist/components/badge/badge.css +46 -0
  22. package/dist/components/badge/badge.d.ts +5 -0
  23. package/dist/components/badge/badge.js +6 -0
  24. package/dist/components/badge/index.d.ts +6 -0
  25. package/dist/components/badge/index.js +3 -0
  26. package/dist/components/breadcrumbs/breadcrumbs.astro +27 -0
  27. package/dist/components/breadcrumbs/breadcrumbs.css +33 -0
  28. package/dist/components/breadcrumbs/breadcrumbs.d.ts +9 -0
  29. package/dist/components/breadcrumbs/breadcrumbs.js +11 -0
  30. package/dist/components/breadcrumbs/index.d.ts +37 -0
  31. package/dist/components/breadcrumbs/index.js +16 -0
  32. package/dist/components/button/button.astro +36 -0
  33. package/dist/components/button/button.css +117 -0
  34. package/dist/components/button/button.d.ts +18 -0
  35. package/dist/components/button/button.js +23 -0
  36. package/dist/components/button/index.d.ts +59 -0
  37. package/dist/components/button/index.js +26 -0
  38. package/dist/components/card/card.astro +98 -0
  39. package/dist/components/card/card.css +91 -0
  40. package/dist/components/card/card.d.ts +19 -0
  41. package/dist/components/card/card.js +7 -0
  42. package/dist/components/card/index.d.ts +11 -0
  43. package/dist/components/card/index.js +7 -0
  44. package/dist/components/description-list/description-list.astro +39 -0
  45. package/dist/components/description-list/description-list.css +72 -0
  46. package/dist/components/description-list/description-list.d.ts +7 -0
  47. package/dist/components/description-list/description-list.js +6 -0
  48. package/dist/components/description-list/index.d.ts +12 -0
  49. package/dist/components/description-list/index.js +5 -0
  50. package/dist/components/divider/divider.astro +27 -0
  51. package/dist/components/divider/divider.css +40 -0
  52. package/dist/components/divider/divider.d.ts +5 -0
  53. package/dist/components/divider/divider.js +11 -0
  54. package/dist/components/divider/index.d.ts +22 -0
  55. package/dist/components/divider/index.js +11 -0
  56. package/dist/components/empty-state/empty-state.astro +47 -0
  57. package/dist/components/empty-state/empty-state.css +38 -0
  58. package/dist/components/empty-state/empty-state.d.ts +12 -0
  59. package/dist/components/empty-state/empty-state.js +7 -0
  60. package/dist/components/empty-state/index.d.ts +5 -0
  61. package/dist/components/empty-state/index.js +3 -0
  62. package/dist/components/field/checkbox-group.astro +58 -0
  63. package/dist/components/field/checkbox.astro +17 -0
  64. package/dist/components/field/field.astro +51 -0
  65. package/dist/components/field/field.css +336 -0
  66. package/dist/components/field/field.d.ts +47 -0
  67. package/dist/components/field/field.js +51 -0
  68. package/dist/components/field/index.d.ts +53 -0
  69. package/dist/components/field/index.js +54 -0
  70. package/dist/components/field/input.astro +27 -0
  71. package/dist/components/field/radio-group.astro +58 -0
  72. package/dist/components/field/radio.astro +17 -0
  73. package/dist/components/field/select.astro +13 -0
  74. package/dist/components/field/switch.astro +17 -0
  75. package/dist/components/field/textarea.astro +24 -0
  76. package/dist/components/filters/filter-layout.astro +72 -0
  77. package/dist/components/filters/filter-panel.astro +13 -0
  78. package/dist/components/filters/filters.css +230 -0
  79. package/dist/components/filters/filters.d.ts +20 -0
  80. package/dist/components/filters/filters.js +24 -0
  81. package/dist/components/filters/index.d.ts +27 -0
  82. package/dist/components/filters/index.js +25 -0
  83. package/dist/components/footer/footer.astro +70 -0
  84. package/dist/components/footer/footer.css +128 -0
  85. package/dist/components/footer/footer.d.ts +16 -0
  86. package/dist/components/footer/footer.js +7 -0
  87. package/dist/components/footer/index.d.ts +35 -0
  88. package/dist/components/footer/index.js +18 -0
  89. package/dist/components/header/header.astro +89 -0
  90. package/dist/components/header/header.css +163 -0
  91. package/dist/components/header/header.d.ts +18 -0
  92. package/dist/components/header/header.js +9 -0
  93. package/dist/components/header/index.d.ts +39 -0
  94. package/dist/components/header/index.js +25 -0
  95. package/dist/components/icon/icon.astro +21 -0
  96. package/dist/components/icon/icon.css +15 -0
  97. package/dist/components/icon/icon.d.ts +5 -0
  98. package/dist/components/icon/icon.js +6 -0
  99. package/dist/components/icon/index.d.ts +57 -0
  100. package/dist/components/icon/index.js +72 -0
  101. package/dist/components/layout/container.astro +15 -0
  102. package/dist/components/layout/grid.astro +14 -0
  103. package/dist/components/layout/index.d.ts +33 -0
  104. package/dist/components/layout/index.js +13 -0
  105. package/dist/components/layout/layout.css +262 -0
  106. package/dist/components/layout/layout.d.ts +24 -0
  107. package/dist/components/layout/layout.js +17 -0
  108. package/dist/components/layout/section-head.astro +42 -0
  109. package/dist/components/layout/section.astro +15 -0
  110. package/dist/components/page-head/index.d.ts +18 -0
  111. package/dist/components/page-head/index.js +14 -0
  112. package/dist/components/page-head/page-head.astro +60 -0
  113. package/dist/components/page-head/page-head.css +79 -0
  114. package/dist/components/page-head/page-head.d.ts +18 -0
  115. package/dist/components/page-head/page-head.js +8 -0
  116. package/dist/components/pagination/index.d.ts +73 -0
  117. package/dist/components/pagination/index.js +87 -0
  118. package/dist/components/pagination/pagination.astro +81 -0
  119. package/dist/components/pagination/pagination.css +70 -0
  120. package/dist/components/pagination/pagination.d.ts +8 -0
  121. package/dist/components/pagination/pagination.js +19 -0
  122. package/dist/components/panel/index.d.ts +11 -0
  123. package/dist/components/panel/index.js +3 -0
  124. package/dist/components/panel/panel.astro +15 -0
  125. package/dist/components/panel/panel.css +48 -0
  126. package/dist/components/panel/panel.d.ts +7 -0
  127. package/dist/components/panel/panel.js +6 -0
  128. package/dist/components/progress/index.d.ts +37 -0
  129. package/dist/components/progress/index.js +44 -0
  130. package/dist/components/progress/progress.astro +18 -0
  131. package/dist/components/progress/progress.css +115 -0
  132. package/dist/components/progress/progress.d.ts +5 -0
  133. package/dist/components/progress/progress.js +7 -0
  134. package/dist/components/prose/code-block.astro +26 -0
  135. package/dist/components/prose/index.d.ts +14 -0
  136. package/dist/components/prose/index.js +67 -0
  137. package/dist/components/prose/prose.astro +14 -0
  138. package/dist/components/prose/prose.css +156 -0
  139. package/dist/components/prose/prose.d.ts +12 -0
  140. package/dist/components/prose/prose.js +11 -0
  141. package/dist/components/stat/index.d.ts +15 -0
  142. package/dist/components/stat/index.js +5 -0
  143. package/dist/components/stat/stat-group.astro +11 -0
  144. package/dist/components/stat/stat.astro +43 -0
  145. package/dist/components/stat/stat.css +51 -0
  146. package/dist/components/stat/stat.d.ts +15 -0
  147. package/dist/components/stat/stat.js +10 -0
  148. package/dist/components/table/index.d.ts +38 -0
  149. package/dist/components/table/index.js +18 -0
  150. package/dist/components/table/table.astro +111 -0
  151. package/dist/components/table/table.css +78 -0
  152. package/dist/components/table/table.d.ts +11 -0
  153. package/dist/components/table/table.js +10 -0
  154. package/dist/components/tabs/index.d.ts +21 -0
  155. package/dist/components/tabs/index.js +13 -0
  156. package/dist/components/tabs/tabs.astro +30 -0
  157. package/dist/components/tabs/tabs.css +59 -0
  158. package/dist/components/tabs/tabs.d.ts +9 -0
  159. package/dist/components/tabs/tabs.js +6 -0
  160. package/dist/components/tag/index.d.ts +19 -0
  161. package/dist/components/tag/index.js +7 -0
  162. package/dist/components/tag/tag.astro +17 -0
  163. package/dist/components/tag/tag.css +30 -0
  164. package/dist/components/tag/tag.d.ts +9 -0
  165. package/dist/components/tag/tag.js +12 -0
  166. package/dist/components/timeline/index.d.ts +22 -0
  167. package/dist/components/timeline/index.js +5 -0
  168. package/dist/components/timeline/timeline.astro +40 -0
  169. package/dist/components/timeline/timeline.css +92 -0
  170. package/dist/components/timeline/timeline.d.ts +9 -0
  171. package/dist/components/timeline/timeline.js +7 -0
  172. package/dist/components/tooltip/index.d.ts +15 -0
  173. package/dist/components/tooltip/index.js +41 -0
  174. package/dist/components/tooltip/tooltip.astro +25 -0
  175. package/dist/components/tooltip/tooltip.css +84 -0
  176. package/dist/components/tooltip/tooltip.d.ts +12 -0
  177. package/dist/components/tooltip/tooltip.js +34 -0
  178. package/dist/config.d.ts +9 -0
  179. package/dist/config.js +6 -0
  180. package/dist/env.d.ts +2 -0
  181. package/dist/html.d.ts +37 -0
  182. package/dist/html.js +126 -0
  183. package/dist/parts.d.ts +27 -0
  184. package/dist/parts.js +28 -0
  185. package/dist/react.d.ts +27 -0
  186. package/dist/react.js +28 -0
  187. package/dist/scss/_chamfer.scss +37 -0
  188. package/dist/scss/_layers.scss +2 -0
  189. package/dist/scss/_theme.scss +15 -0
  190. package/dist/themes/eve-online.css +1945 -0
  191. package/dist/themes/xaroth.css +1974 -0
  192. package/dist/themes.css +3919 -0
  193. package/package.json +80 -0
@@ -0,0 +1,24 @@
1
+ ---
2
+ import './field.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { textareaClass } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'textarea'>, 'class' | 'value'> & {
7
+ /** Initial text. The default slot works too. */
8
+ value?: string
9
+ defaultValue?: string
10
+ class?: string
11
+ }
12
+
13
+ const { value, defaultValue, class: className, ...rest } = Astro.props
14
+ const text = value ?? defaultValue
15
+ const attrs = { ...rest, class: textareaClass({ className }) }
16
+ ---
17
+
18
+ {
19
+ text === undefined ? (
20
+ <textarea {...attrs}><slot /></textarea>
21
+ ) : (
22
+ <textarea {...attrs}>{text}</textarea>
23
+ )
24
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ import './filters.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import {
5
+ filterClasses as c,
6
+ filterLayoutClass,
7
+ filterPositions,
8
+ filterSwitchButtonAttrs,
9
+ filterSwitchLabels,
10
+ filterSwitchScript,
11
+ type FilterLayoutOptions,
12
+ } from './index.js'
13
+
14
+ type Props = Omit<HTMLAttributes<'div'>, 'class'> &
15
+ Omit<FilterLayoutOptions, 'className'> & {
16
+ class?: string
17
+ /** Adds a Top / Side switch above the filters (hidden below 1024px). Uses a tiny inline script. */
18
+ switchable?: boolean
19
+ switchLabel?: string
20
+ /** Below 1024px the filters fold behind a closed disclosure. */
21
+ collapsible?: boolean
22
+ collapsibleLabel?: string
23
+ /** Number of active filters, shown on the disclosure. */
24
+ activeCount?: number
25
+ activeLabel?: string
26
+ }
27
+
28
+ const {
29
+ position = 'top',
30
+ switchable,
31
+ switchLabel = 'Filter position',
32
+ collapsible,
33
+ collapsibleLabel = 'Filters',
34
+ activeCount,
35
+ activeLabel = 'active',
36
+ class: className,
37
+ ...rest
38
+ } = Astro.props
39
+ ---
40
+
41
+ <div class={filterLayoutClass({ position, className })} {...rest}>
42
+ {
43
+ switchable && (
44
+ <fieldset class={c.switch}>
45
+ <legend class={c.switchLabel}>{switchLabel}</legend>
46
+ <span class={c.switchOptions}>
47
+ {filterPositions.map((option) => (
48
+ <button type="button" {...filterSwitchButtonAttrs(option, position)}>{filterSwitchLabels[option]}</button>
49
+ ))}
50
+ </span>
51
+ </fieldset>
52
+ )
53
+ }
54
+ {
55
+ collapsible && (
56
+ <details class={c.disclosure}>
57
+ <summary class={c.toggle}>
58
+ {collapsibleLabel}
59
+ {activeCount !== undefined && activeCount > 0 && (
60
+ <span class={c.count}>
61
+ {activeCount}
62
+ <span class="x-visually-hidden">{` ${activeLabel}`}</span>
63
+ </span>
64
+ )}
65
+ </summary>
66
+ </details>
67
+ )
68
+ }
69
+ <div class={c.controls}><slot name="filters" /></div>
70
+ <div class={c.results}><slot /></div>
71
+ </div>
72
+ {switchable && <script is:inline set:html={filterSwitchScript} />}
@@ -0,0 +1,13 @@
1
+ ---
2
+ import './filters.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { filterPanelClass } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'form'>, 'class'> & {
7
+ class?: string
8
+ }
9
+
10
+ const { class: className, ...rest } = Astro.props
11
+ ---
12
+
13
+ <form class={filterPanelClass({ className })} {...rest}><slot /></form>
@@ -0,0 +1,230 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-filters,
4
+ .x-filter-panel {
5
+ --x-filters-field-gap: var(--x-space-5);
6
+ }
7
+ .x-filters {
8
+ --x-filters-switch-font: var(--x-font-ui);
9
+ --x-filters-switch-fs: var(--x-fs-label);
10
+ --x-filters-switch-weight: 600;
11
+ --x-filters-switch-tracking: var(--x-track-label);
12
+ --x-filters-switch-case: uppercase;
13
+ --x-filters-switch-color: var(--x-color-text-muted);
14
+ --x-filters-option-font: var(--x-font-ui);
15
+ --x-filters-option-weight: 700;
16
+ --x-filters-option-tracking: var(--x-track-label);
17
+ --x-filters-option-color: var(--x-color-text-muted);
18
+ --x-filters-option-bg: transparent;
19
+ --x-filters-option-hover: var(--x-color-text);
20
+ --x-filters-option-on-color: var(--x-color-on-accent);
21
+ --x-filters-option-on-bg: var(--x-color-accent);
22
+ --x-filters-option-on-shadow: none;
23
+ --x-filters-toggle-bg: transparent;
24
+ --x-filters-count-color: var(--x-color-accent);
25
+ display: grid;
26
+ grid-template-columns: minmax(0, 1fr);
27
+ gap: var(--x-space-6) var(--x-gap);
28
+ align-items: start;
29
+ }
30
+ .x-filters__switch {
31
+ display: none;
32
+ grid-column: 1/-1;
33
+ min-width: 0;
34
+ padding: 0;
35
+ border: 0;
36
+ flex-wrap: wrap;
37
+ gap: var(--x-space-3);
38
+ align-items: center;
39
+ margin-bottom: calc(var(--x-space-4) - var(--x-space-6));
40
+ }
41
+ @media (min-width: 1024px) {
42
+ .x-filters__switch {
43
+ display: flex;
44
+ }
45
+ }
46
+ .x-filters__switch-label {
47
+ float: left;
48
+ padding: 0;
49
+ font-family: var(--x-filters-switch-font);
50
+ font-size: var(--x-filters-switch-fs);
51
+ font-weight: var(--x-filters-switch-weight);
52
+ line-height: 1.3;
53
+ letter-spacing: var(--x-filters-switch-tracking);
54
+ text-transform: var(--x-filters-switch-case);
55
+ color: var(--x-filters-switch-color);
56
+ }
57
+ .x-filters__switch-options {
58
+ display: inline-flex;
59
+ border: 1px solid var(--x-color-line-strong);
60
+ border-radius: var(--x-radius);
61
+ }
62
+ .x-filters__switch-option {
63
+ min-width: 64px;
64
+ height: var(--x-control-sm);
65
+ padding: 0 var(--x-space-4);
66
+ margin: 0;
67
+ font-family: var(--x-filters-option-font);
68
+ font-size: var(--x-fs-label);
69
+ font-weight: var(--x-filters-option-weight);
70
+ line-height: 1;
71
+ letter-spacing: var(--x-filters-option-tracking);
72
+ text-transform: uppercase;
73
+ color: var(--x-filters-option-color);
74
+ cursor: pointer;
75
+ background: var(--x-filters-option-bg);
76
+ border: 0;
77
+ transition: color var(--x-duration) var(--x-ease), background-color var(--x-duration) var(--x-ease);
78
+ }
79
+ .x-filters__switch-option + .x-filters__switch-option {
80
+ border-left: 1px solid var(--x-color-line-strong);
81
+ }
82
+ .x-filters__switch-option:hover {
83
+ color: var(--x-filters-option-hover);
84
+ }
85
+ .x-filters__switch-option:focus-visible {
86
+ position: relative;
87
+ outline: var(--x-focus-width) solid var(--x-color-focus);
88
+ outline-offset: var(--x-focus-offset);
89
+ }
90
+ .x-filters__switch-option[aria-pressed=true] {
91
+ color: var(--x-filters-option-on-color);
92
+ background: var(--x-filters-option-on-bg);
93
+ box-shadow: var(--x-filters-option-on-shadow);
94
+ }
95
+ .x-filters__disclosure {
96
+ display: none;
97
+ min-width: 0;
98
+ margin-bottom: calc(var(--x-space-3) - var(--x-space-6));
99
+ }
100
+ @media (width < 1024px) {
101
+ .x-filters__disclosure {
102
+ display: block;
103
+ }
104
+ .x-filters__disclosure:not([open]) {
105
+ margin-bottom: 0;
106
+ }
107
+ .x-filters__disclosure:not([open]) + .x-filters__controls {
108
+ display: none;
109
+ }
110
+ }
111
+ .x-filters__toggle {
112
+ display: flex;
113
+ gap: var(--x-space-3);
114
+ align-items: center;
115
+ min-height: var(--x-control-md);
116
+ padding: 0 var(--x-space-4);
117
+ font-family: var(--x-filters-switch-font);
118
+ font-size: var(--x-filters-switch-fs);
119
+ font-weight: var(--x-filters-switch-weight);
120
+ line-height: 1;
121
+ letter-spacing: var(--x-filters-switch-tracking);
122
+ text-transform: var(--x-filters-switch-case);
123
+ color: var(--x-color-text);
124
+ list-style: none;
125
+ cursor: pointer;
126
+ background: var(--x-filters-toggle-bg);
127
+ border: 1px solid var(--x-color-line-strong);
128
+ border-radius: var(--x-radius);
129
+ transition: border-color var(--x-duration) var(--x-ease);
130
+ }
131
+ .x-filters__toggle::-webkit-details-marker {
132
+ display: none;
133
+ }
134
+ .x-filters__toggle::after {
135
+ content: "";
136
+ width: 7px;
137
+ height: 7px;
138
+ margin: -4px 2px 0 auto;
139
+ border-right: 1.5px solid currentcolor;
140
+ border-bottom: 1.5px solid currentcolor;
141
+ transform: rotate(45deg);
142
+ }
143
+ [open] > .x-filters__toggle::after {
144
+ margin-top: 4px;
145
+ transform: rotate(-135deg);
146
+ }
147
+ .x-filters__toggle:hover {
148
+ border-color: var(--x-color-accent-edge);
149
+ }
150
+ .x-filters__toggle:focus-visible {
151
+ outline: var(--x-focus-width) solid var(--x-color-focus);
152
+ outline-offset: var(--x-focus-offset);
153
+ }
154
+ .x-filters__count {
155
+ display: inline-grid;
156
+ place-items: center;
157
+ min-width: 1.5rem;
158
+ padding: 0 var(--x-space-1);
159
+ font-family: var(--x-font-mono);
160
+ font-size: var(--x-fs-label);
161
+ font-weight: 400;
162
+ line-height: 1.5;
163
+ letter-spacing: normal;
164
+ color: var(--x-filters-count-color);
165
+ border: 1px solid var(--x-color-accent-edge);
166
+ }
167
+ .x-filters__controls, .x-filters__results {
168
+ min-width: 0;
169
+ }
170
+ @media (min-width: 1024px) {
171
+ .x-filters--side {
172
+ grid-template-columns: repeat(12, minmax(0, 1fr));
173
+ }
174
+ .x-filters--side .x-filters__controls {
175
+ grid-column: span 3;
176
+ }
177
+ .x-filters--side .x-filters__results {
178
+ grid-column: span 9;
179
+ }
180
+ }
181
+ .x-filter-panel {
182
+ display: grid;
183
+ gap: var(--x-filters-field-gap) var(--x-gap);
184
+ align-content: start;
185
+ min-width: 0;
186
+ margin: 0;
187
+ }
188
+ @media (min-width: 640px) {
189
+ .x-filter-panel {
190
+ grid-template-columns: repeat(2, minmax(0, 1fr));
191
+ }
192
+ }
193
+ .x-filter-panel > .x-checkbox,
194
+ .x-filter-panel > .x-switch {
195
+ align-self: end;
196
+ min-height: var(--x-control-md);
197
+ }
198
+ @media (min-width: 1024px) {
199
+ .x-filters--side .x-filter-panel {
200
+ grid-template-columns: minmax(0, 1fr);
201
+ }
202
+ .x-filters--side .x-filter-panel .x-choice-group__options {
203
+ flex-direction: column;
204
+ }
205
+ .x-filters--side .x-filter-panel > .x-checkbox,
206
+ .x-filters--side .x-filter-panel > .x-switch {
207
+ min-height: var(--x-control-sm);
208
+ }
209
+ .x-filters--top .x-filter-panel {
210
+ display: flex;
211
+ flex-wrap: wrap;
212
+ align-items: end;
213
+ }
214
+ .x-filters--top .x-filter-panel > * {
215
+ flex: 1 1 14rem;
216
+ }
217
+ .x-filters--top .x-filter-panel > .x-choice-group {
218
+ flex: 1.6 1 auto;
219
+ }
220
+ .x-filters--top .x-filter-panel > .x-checkbox,
221
+ .x-filters--top .x-filter-panel > .x-switch {
222
+ flex: none;
223
+ margin-left: auto;
224
+ }
225
+ .x-filters--top .x-filter-panel .x-choice-group__options {
226
+ align-items: center;
227
+ min-height: var(--x-control-md);
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,20 @@
1
+ import './filters.scss';
2
+ import { type FormHTMLAttributes, type HTMLAttributes, type ReactNode } from 'react';
3
+ import { type FilterLayoutOptions, type FilterPosition } from './index.ts';
4
+ export type FilterLayoutProps = HTMLAttributes<HTMLDivElement> & Omit<FilterLayoutOptions, 'className'> & {
5
+ /** The filter form, usually a FilterPanel. */
6
+ filters?: ReactNode;
7
+ /** Adds a Top / Side switch above the filters (hidden below 1024px). */
8
+ switchable?: boolean;
9
+ switchLabel?: string;
10
+ /** Below 1024px the filters fold behind a closed disclosure. */
11
+ collapsible?: boolean;
12
+ collapsibleLabel?: string;
13
+ /** Number of active filters, shown on the disclosure. */
14
+ activeCount?: number;
15
+ activeLabel?: string;
16
+ onPositionChange?: (position: FilterPosition) => void;
17
+ };
18
+ export declare function FilterLayout({ position, switchable, switchLabel, collapsible, collapsibleLabel, activeCount, activeLabel, onPositionChange, filters, className, children, ...rest }: FilterLayoutProps): import("react").JSX.Element;
19
+ export type FilterPanelProps = FormHTMLAttributes<HTMLFormElement>;
20
+ export declare function FilterPanel({ className, ...rest }: FilterPanelProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import './filters.css';
4
+ import { useState } from 'react';
5
+ import { filterClasses as c, filterLayoutClass, filterPanelClass, filterPositions, filterSwitchButtonAttrs, filterSwitchLabels, } from "./index.js";
6
+ export function FilterLayout({ position = 'top', switchable, switchLabel = 'Filter position', collapsible, collapsibleLabel = 'Filters', activeCount, activeLabel = 'active', onPositionChange, filters, className, children, ...rest }) {
7
+ const [current, setCurrent] = useState(position);
8
+ // A new position prop resets the switch; clicks change it until then.
9
+ const [prop, setProp] = useState(position);
10
+ if (prop !== position) {
11
+ setProp(position);
12
+ setCurrent(position);
13
+ }
14
+ return (_jsxs("div", { ...rest, className: filterLayoutClass({ position: current, className }), children: [switchable && (_jsxs("fieldset", { className: c.switch, children: [_jsx("legend", { className: c.switchLabel, children: switchLabel }), _jsx("span", { className: c.switchOptions, children: filterPositions.map((option) => {
15
+ const { class: optionClass, ...attrs } = filterSwitchButtonAttrs(option, current);
16
+ return (_jsx("button", { ...attrs, type: "button", className: optionClass, onClick: () => {
17
+ setCurrent(option);
18
+ onPositionChange?.(option);
19
+ }, children: filterSwitchLabels[option] }, option));
20
+ }) })] })), collapsible && (_jsx("details", { className: c.disclosure, children: _jsxs("summary", { className: c.toggle, children: [collapsibleLabel, activeCount !== undefined && activeCount > 0 && (_jsxs("span", { className: c.count, children: [activeCount, _jsx("span", { className: "x-visually-hidden", children: ` ${activeLabel}` })] }))] }) })), _jsx("div", { className: c.controls, children: filters }), _jsx("div", { className: c.results, children: children })] }));
21
+ }
22
+ export function FilterPanel({ className, ...rest }) {
23
+ return (_jsx("form", { ...rest, className: filterPanelClass({ className }) }));
24
+ }
@@ -0,0 +1,27 @@
1
+ export type FilterPosition = 'top' | 'side';
2
+ export declare const filterPositions: readonly FilterPosition[];
3
+ export type FilterLayoutOptions = {
4
+ position?: FilterPosition;
5
+ className?: string;
6
+ };
7
+ export declare const filterLayoutClass: ({ position, className }?: FilterLayoutOptions) => string;
8
+ export declare const filterPanelClass: ({ className }?: {
9
+ className?: string;
10
+ }) => string;
11
+ export declare const filterClasses: {
12
+ readonly switch: string;
13
+ readonly switchLabel: string;
14
+ readonly switchOptions: string;
15
+ readonly disclosure: string;
16
+ readonly toggle: string;
17
+ readonly count: string;
18
+ readonly controls: string;
19
+ readonly results: string;
20
+ };
21
+ export declare const filterSwitchLabels: Record<FilterPosition, string>;
22
+ export declare const filterSwitchButtonAttrs: (option: FilterPosition, position?: FilterPosition) => {
23
+ class: string;
24
+ 'data-x-filters-position': FilterPosition;
25
+ 'aria-pressed': "true" | "false";
26
+ };
27
+ export declare const filterSwitchScript = "(()=>{if(window.__xFilters)return;window.__xFilters=1;document.addEventListener('click',(e)=>{const b=e.target instanceof Element&&e.target.closest('[data-x-filters-position]');const r=b&&b.closest('.x-filters');if(!r)return;const p=b.getAttribute('data-x-filters-position');r.classList.toggle('x-filters--top',p==='top');r.classList.toggle('x-filters--side',p==='side');r.querySelectorAll(':scope>.x-filters__switch [data-x-filters-position]').forEach((o)=>o.setAttribute('aria-pressed',String(o===b)))})})()";
@@ -0,0 +1,25 @@
1
+ import { bem } from "../../bem.js";
2
+ export const filterPositions = ['top', 'side'];
3
+ const filters = bem('x-filters');
4
+ const filterPanel = bem('x-filter-panel');
5
+ export const filterLayoutClass = ({ position = 'top', className } = {}) => filters({ position }, className);
6
+ export const filterPanelClass = ({ className } = {}) => filterPanel({}, className);
7
+ export const filterClasses = {
8
+ switch: filters.el('switch'),
9
+ switchLabel: filters.el('switch-label'),
10
+ switchOptions: filters.el('switch-options'),
11
+ disclosure: filters.el('disclosure'),
12
+ toggle: filters.el('toggle'),
13
+ count: filters.el('count'),
14
+ controls: filters.el('controls'),
15
+ results: filters.el('results'),
16
+ };
17
+ export const filterSwitchLabels = { top: 'Top', side: 'Side' };
18
+ export const filterSwitchButtonAttrs = (option, position = 'top') => ({
19
+ class: filters.el('switch-option'),
20
+ 'data-x-filters-position': option,
21
+ 'aria-pressed': option === position ? 'true' : 'false',
22
+ });
23
+ // Astro only: flips the modifier class and aria-pressed on click. One document listener serves every instance,
24
+ // so repeated copies of the script return early.
25
+ export const filterSwitchScript = `(()=>{if(window.__xFilters)return;window.__xFilters=1;document.addEventListener('click',(e)=>{const b=e.target instanceof Element&&e.target.closest('[data-x-filters-position]');const r=b&&b.closest('.x-filters');if(!r)return;const p=b.getAttribute('data-x-filters-position');r.classList.toggle('x-filters--top',p==='top');r.classList.toggle('x-filters--side',p==='side');r.querySelectorAll(':scope>.x-filters__switch [data-x-filters-position]').forEach((o)=>o.setAttribute('aria-pressed',String(o===b)))})})()`;
@@ -0,0 +1,70 @@
1
+ ---
2
+ import './footer.css'
3
+ import type { HTMLAttributes } from 'astro/types'
4
+ import { footerBrandState, footerClass, footerClasses as c, type FooterGroup, type FooterOptions } from './index.js'
5
+
6
+ type Props = Omit<HTMLAttributes<'footer'>, 'class'> &
7
+ Omit<FooterOptions, 'className'> & {
8
+ class?: string
9
+ groups?: FooterGroup[]
10
+ brandHref?: string
11
+ /** Accessible name for the brand link. Ignored without `brandHref`. */
12
+ brandLabel?: string
13
+ /** Legal line. The `note` slot takes precedence. */
14
+ note?: string
15
+ navLabel?: string
16
+ }
17
+
18
+ const {
19
+ layout,
20
+ class: className,
21
+ groups = [],
22
+ brandHref,
23
+ brandLabel,
24
+ note,
25
+ navLabel = 'Footer',
26
+ slot: _slot,
27
+ ...rest
28
+ } = Astro.props
29
+ const { tag: Brand, attrs: brand } = footerBrandState(brandHref, brandLabel)
30
+ const hasNote = Astro.slots.has('note') || Boolean(note)
31
+ ---
32
+
33
+ <footer {...rest} class={footerClass({ layout, className })}>
34
+ <div class={c.inner}>
35
+ {
36
+ Astro.slots.has('brand') && (
37
+ <Brand {...brand} class={c.brand}>
38
+ <slot name="brand" />
39
+ </Brand>
40
+ )
41
+ }
42
+ {
43
+ hasNote && (
44
+ <p class={c.note}>
45
+ <slot name="note">{note}</slot>
46
+ </p>
47
+ )
48
+ }
49
+ {
50
+ groups.length > 0 && (
51
+ <nav class={c.nav} aria-label={navLabel}>
52
+ {groups.map((group) => (
53
+ <div class={c.group}>
54
+ {group.title && <h2 class={c.title}>{group.title}</h2>}
55
+ <ul class={c.links}>
56
+ {group.links.map((link) => (
57
+ <li>
58
+ <a class={c.link} href={link.href}>
59
+ {link.label}
60
+ </a>
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ </div>
65
+ ))}
66
+ </nav>
67
+ )
68
+ }
69
+ </div>
70
+ </footer>
@@ -0,0 +1,128 @@
1
+ @layer x.reset, x.core, x.theme, x.utility;
2
+ @layer x.core {
3
+ .x-footer__inner {
4
+ display: grid;
5
+ width: 100%;
6
+ max-width: calc(var(--x-page) + 2 * var(--x-gutter));
7
+ padding-inline: var(--x-gutter);
8
+ margin-inline: auto;
9
+ }
10
+ .x-footer__brand {
11
+ display: inline-flex;
12
+ gap: 10px;
13
+ align-items: center;
14
+ justify-self: start;
15
+ color: inherit;
16
+ text-decoration: none;
17
+ }
18
+ .x-footer__brand svg {
19
+ flex: none;
20
+ width: var(--x-footer-mark, 28px);
21
+ height: var(--x-footer-mark, 28px);
22
+ }
23
+ .x-footer__note {
24
+ max-width: var(--x-prose);
25
+ margin: 0;
26
+ font-size: var(--x-fs-small);
27
+ line-height: 1.5;
28
+ }
29
+ .x-footer__group {
30
+ display: grid;
31
+ gap: var(--x-space-3);
32
+ align-content: start;
33
+ min-width: 0;
34
+ }
35
+ .x-footer__title {
36
+ margin: 0;
37
+ font-size: var(--x-fs-label);
38
+ line-height: 1.3;
39
+ }
40
+ .x-footer__links {
41
+ display: grid;
42
+ gap: var(--x-space-2);
43
+ padding: 0;
44
+ margin: 0;
45
+ list-style: none;
46
+ }
47
+ .x-footer__link {
48
+ text-decoration: none;
49
+ }
50
+ .x-footer__link:hover {
51
+ text-decoration: underline;
52
+ }
53
+ .x-footer--columns .x-footer__inner {
54
+ grid-template-columns: minmax(0, 1fr);
55
+ gap: var(--x-space-3) var(--x-gap);
56
+ }
57
+ .x-footer--columns .x-footer__note {
58
+ max-width: 30rem;
59
+ }
60
+ .x-footer--columns .x-footer__nav {
61
+ display: grid;
62
+ grid-template-columns: repeat(2, minmax(0, 1fr));
63
+ gap: var(--x-space-6) var(--x-gap);
64
+ margin-top: var(--x-space-5);
65
+ }
66
+ @media (width >= 1024px) {
67
+ .x-footer--columns .x-footer__inner {
68
+ grid-template-rows: auto 1fr;
69
+ grid-template-columns: repeat(2, minmax(0, 1fr));
70
+ }
71
+ .x-footer--columns .x-footer__brand,
72
+ .x-footer--columns .x-footer__note {
73
+ grid-column: 1;
74
+ }
75
+ .x-footer--columns .x-footer__note {
76
+ align-self: start;
77
+ }
78
+ .x-footer--columns .x-footer__nav {
79
+ grid-auto-columns: minmax(0, 1fr);
80
+ grid-auto-flow: column;
81
+ grid-row: 1/span 2;
82
+ grid-column: 2;
83
+ grid-template-columns: none;
84
+ margin-top: 0;
85
+ }
86
+ }
87
+ .x-footer--row .x-footer__inner {
88
+ grid-template-columns: 1fr auto;
89
+ gap: var(--x-space-5);
90
+ align-items: center;
91
+ }
92
+ .x-footer--row .x-footer__brand {
93
+ grid-row: 1;
94
+ grid-column: 1;
95
+ }
96
+ .x-footer--row .x-footer__nav {
97
+ display: flex;
98
+ flex-wrap: wrap;
99
+ gap: var(--x-space-2) var(--x-space-5);
100
+ grid-row: 1;
101
+ grid-column: 2;
102
+ }
103
+ .x-footer--row .x-footer__note {
104
+ grid-column: 1/-1;
105
+ }
106
+ .x-footer--row .x-footer__title {
107
+ position: absolute;
108
+ width: 1px;
109
+ height: 1px;
110
+ overflow: hidden;
111
+ clip-path: inset(50%);
112
+ white-space: nowrap;
113
+ }
114
+ .x-footer--row .x-footer__links {
115
+ display: flex;
116
+ flex-wrap: wrap;
117
+ gap: var(--x-space-2) var(--x-space-5);
118
+ }
119
+ @media (width < 640px) {
120
+ .x-footer--row .x-footer__inner {
121
+ grid-template-columns: 1fr;
122
+ }
123
+ .x-footer--row .x-footer__nav {
124
+ grid-row: 2;
125
+ grid-column: 1;
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,16 @@
1
+ import './footer.scss';
2
+ import type { HTMLAttributes, ReactNode } from 'react';
3
+ import { type FooterGroup, type FooterOptions } from './index.ts';
4
+ export type FooterProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & Omit<FooterOptions, 'className'> & {
5
+ /** Logo and site name. */
6
+ brand?: ReactNode;
7
+ /** Makes the brand a link, usually to the home page. */
8
+ brandHref?: string;
9
+ /** Accessible name for the brand link. Ignored without `brandHref`. */
10
+ brandLabel?: string;
11
+ /** Legal line. */
12
+ note?: ReactNode;
13
+ groups?: FooterGroup[];
14
+ navLabel?: string;
15
+ };
16
+ export declare function Footer({ layout, className, brand, brandHref, brandLabel, note, groups, navLabel, ...rest }: FooterProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './footer.css';
3
+ import { footerBrandState, footerClass, footerClasses as c } from "./index.js";
4
+ export function Footer({ layout, className, brand, brandHref, brandLabel, note, groups = [], navLabel = 'Footer', ...rest }) {
5
+ const { tag: Brand, attrs: brandAttrs } = footerBrandState(brandHref, brandLabel);
6
+ return (_jsx("footer", { ...rest, className: footerClass({ layout, className }), children: _jsxs("div", { className: c.inner, children: [brand != null && (_jsx(Brand, { ...brandAttrs, className: c.brand, children: brand })), note != null && note !== '' && _jsx("p", { className: c.note, children: note }), groups.length > 0 && (_jsx("nav", { className: c.nav, "aria-label": navLabel, children: groups.map((group, i) => (_jsxs("div", { className: c.group, children: [group.title && _jsx("h2", { className: c.title, children: group.title }), _jsx("ul", { className: c.links, children: group.links.map((link) => (_jsx("li", { children: _jsx("a", { className: c.link, href: link.href, children: link.label }) }, link.href + link.label))) })] }, group.title ?? i))) }))] }) }));
7
+ }