webcoreui 0.5.0 → 0.6.1

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 (74) hide show
  1. package/README.md +7 -0
  2. package/astro.d.ts +115 -68
  3. package/astro.js +8 -0
  4. package/components/Badge/Badge.astro +4 -0
  5. package/components/Badge/Badge.svelte +5 -1
  6. package/components/Badge/Badge.tsx +4 -0
  7. package/components/Badge/badge.module.scss +8 -0
  8. package/components/Badge/badge.ts +7 -3
  9. package/components/Breadcrumb/Breadcrumb.astro +51 -0
  10. package/components/Breadcrumb/Breadcrumb.svelte +45 -0
  11. package/components/Breadcrumb/Breadcrumb.tsx +51 -0
  12. package/components/Breadcrumb/breadcrumb.module.scss +26 -0
  13. package/components/Breadcrumb/breadcrumb.ts +12 -0
  14. package/components/Button/button.ts +13 -3
  15. package/components/Carousel/Carousel.astro +16 -7
  16. package/components/Carousel/Carousel.svelte +15 -5
  17. package/components/Carousel/Carousel.tsx +15 -5
  18. package/components/Carousel/carousel.module.scss +4 -1
  19. package/components/Carousel/carousel.ts +1 -1
  20. package/components/Checkbox/checkbox.ts +4 -2
  21. package/components/DataTable/DataTable.astro +2 -2
  22. package/components/DataTable/DataTable.tsx +287 -287
  23. package/components/Footer/Footer.astro +91 -0
  24. package/components/Footer/Footer.svelte +94 -0
  25. package/components/Footer/Footer.tsx +107 -0
  26. package/components/Footer/footer.module.scss +61 -0
  27. package/components/Footer/footer.ts +29 -0
  28. package/components/Icon/Icon.svelte +1 -1
  29. package/components/Icon/icon.ts +18 -1
  30. package/components/Icon/map.ts +8 -0
  31. package/components/List/list.ts +3 -1
  32. package/components/Masonry/Masonry.astro +54 -0
  33. package/components/Masonry/Masonry.svelte +54 -0
  34. package/components/Masonry/Masonry.tsx +62 -0
  35. package/components/Masonry/masonry.module.scss +18 -0
  36. package/components/Masonry/masonry.ts +36 -0
  37. package/components/Menu/Menu.astro +1 -1
  38. package/components/Menu/Menu.svelte +1 -1
  39. package/components/Menu/Menu.tsx +1 -1
  40. package/components/Menu/menu.ts +4 -2
  41. package/components/Modal/Modal.astro +2 -0
  42. package/components/Modal/Modal.svelte +2 -0
  43. package/components/Modal/Modal.tsx +2 -0
  44. package/components/Modal/modal.module.scss +29 -22
  45. package/components/Modal/modal.ts +1 -0
  46. package/components/Pagination/Pagination.astro +2 -0
  47. package/components/Pagination/Pagination.svelte +6 -3
  48. package/components/Pagination/Pagination.tsx +5 -3
  49. package/components/Pagination/pagination.ts +1 -0
  50. package/components/Rating/rating.ts +3 -1
  51. package/components/Sidebar/Sidebar.astro +61 -0
  52. package/components/Sidebar/Sidebar.svelte +50 -0
  53. package/components/Sidebar/Sidebar.tsx +58 -0
  54. package/components/Sidebar/sidebar.module.scss +43 -0
  55. package/components/Sidebar/sidebar.ts +21 -0
  56. package/components/Switch/switch.ts +4 -2
  57. package/icons/circle-close.svg +3 -0
  58. package/icons/components.svg +3 -0
  59. package/icons/file.svg +3 -0
  60. package/icons/home.svg +4 -0
  61. package/icons/sun.svg +1 -1
  62. package/icons.d.ts +4 -0
  63. package/icons.js +4 -0
  64. package/index.d.ts +8 -0
  65. package/index.js +1 -0
  66. package/package.json +9 -10
  67. package/react.d.ts +115 -68
  68. package/react.js +8 -0
  69. package/scss/config/typography.scss +3 -1
  70. package/scss/global/utility.scss +1 -1
  71. package/svelte.d.ts +116 -68
  72. package/svelte.js +8 -0
  73. package/utils/DOMUtils.ts +28 -0
  74. package/scss/global/elements.scss +0 -31
@@ -1,287 +1,287 @@
1
- import React, { useState } from 'react'
2
- import type { HeadingObject, ReactDataTableProps } from './datatable'
3
-
4
- import Button from '../Button/Button.tsx'
5
- import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
6
- import Input from '../Input/Input.tsx'
7
- import Pagination from '../Pagination/Pagination.tsx'
8
- import Select from '../Select/Select.tsx'
9
-
10
- import { classNames } from '../../utils/classNames'
11
- import { debounce } from '../../utils/debounce'
12
-
13
- import checkIcon from '../../icons/check.svg?raw'
14
- import orderIcon from '../../icons/order.svg?raw'
15
- import searchIcon from '../../icons/search.svg?raw'
16
-
17
- import styles from './datatable.module.scss'
18
-
19
- import type { ListEventType } from '../List/list'
20
-
21
- // eslint-disable-next-line complexity
22
- const DataTable = ({
23
- headings,
24
- filterPlaceholder = 'Filter entries',
25
- showFilterIcon,
26
- noResultsLabel = 'No results.',
27
- itemsPerPage,
28
- subText,
29
- columnToggleLabel = 'Columns',
30
- pagination,
31
- data,
32
- hover,
33
- striped,
34
- offsetStripe,
35
- compact,
36
- maxHeight,
37
- className,
38
- id,
39
- onFilter,
40
- children
41
- }: ReactDataTableProps) => {
42
- const [filteredData, setFilteredData] = useState<any>(data)
43
- const [toggledData, setToggledData] = useState(filteredData)
44
- const [filteredHeadings, setFilteredHeadings] = useState<any>(headings)
45
- const [page, setPage] = useState(1)
46
- const [hasActiveFilter, setHasActiveFilter] = useState(false)
47
- const [sortOrder, setSortOrder] = useState(1)
48
-
49
- const classes = classNames([
50
- styles.table,
51
- hover && styles.hover,
52
- striped && styles[`striped-${striped}s`],
53
- offsetStripe && styles.offset,
54
- compact && styles.compact,
55
- maxHeight && styles.scroll
56
- ])
57
-
58
- const footerClasses = classNames([
59
- styles.footer,
60
- subText && styles.between
61
- ])
62
-
63
- const styleVariables = {
64
- ...(maxHeight && { 'max-height': maxHeight })
65
- } as React.CSSProperties
66
-
67
- const showColumnToggle = headings?.some(heading => {
68
- return typeof heading === 'string' ? false : heading.toggleable
69
- })
70
-
71
- const columnToggleItems = [{
72
- items: headings?.length ? headings
73
- .filter(heading => typeof heading !== 'string' && heading.toggleable)
74
- .map(heading => ({
75
- icon: checkIcon,
76
- name: (heading as HeadingObject).name,
77
- value: String(headings.findIndex(h => {
78
- return (h as HeadingObject).name === (heading as HeadingObject).name
79
- }))
80
- })) : []
81
- }]
82
-
83
- const columnFilterIndexes = headings?.map(heading => (heading as HeadingObject).filterable)
84
- .map((heading, index) => heading ? index : null)
85
- .filter(heading => heading !== null) || []
86
-
87
- const hasPagination = data?.length && itemsPerPage
88
- ? data.length > itemsPerPage
89
- : false
90
-
91
- const filter = debounce((event: Event) => {
92
- const target = event.target as HTMLInputElement
93
-
94
- setHasActiveFilter(!!target.value)
95
-
96
- setFilteredData(toggledData?.filter((row: string[]) => {
97
- const rowValue = row.filter((_, index) => columnFilterIndexes.includes(index))
98
- .join('')
99
- .toLowerCase()
100
-
101
- return rowValue.includes(target.value.toLowerCase())
102
- }))
103
-
104
- onFilter?.({
105
- results: filteredData,
106
- numberOfResults: filteredData.length
107
- })
108
- }, 400)
109
-
110
- const toggleColumns = (event: ListEventType) => {
111
- const columnToggleListElement = Array.from(event.list.children)
112
- .find(child => (child as HTMLLIElement).dataset.name === event.name) as HTMLLIElement
113
- const svgIcon = columnToggleListElement.children[0] as HTMLElement
114
- let mappedData
115
-
116
- svgIcon.style.opacity = svgIcon.style.opacity === '0'
117
- ? '1'
118
- : '0'
119
-
120
- if (svgIcon.style.opacity === '0') {
121
- mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[]) => {
122
- return row.map((column, index) => index === Number(event.value) ? null : column)
123
- })
124
-
125
- setFilteredData(mappedData)
126
-
127
- setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string) => {
128
- return ((heading as HeadingObject)?.name || heading) === event.name ? null : heading
129
- }))
130
- } else {
131
- mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[], x: number) => {
132
- return row.map((column, y) => y === Number(event.value) ? data?.[x][y] : column)
133
- })
134
-
135
- setFilteredData(mappedData)
136
-
137
- setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string, index: number) => {
138
- return ((headings?.[index] as HeadingObject)?.name || headings?.[index]) === event.name
139
- ? headings?.[index]
140
- : heading
141
- }))
142
- }
143
-
144
- setToggledData(mappedData)
145
- }
146
-
147
- const sort = (index: number) => {
148
- const sortedData = filteredData.sort((a: string[], b: string[]) => {
149
- let aValue: string | number = a[index]
150
- let bValue: string | number = b[index]
151
-
152
- if (!isNaN(aValue as any)) {
153
- aValue = Number(aValue)
154
- }
155
-
156
- if (!isNaN(bValue as any)) {
157
- bValue = Number(bValue)
158
- }
159
-
160
- return aValue > bValue
161
- ? sortOrder * -1
162
- : sortOrder
163
- })
164
-
165
- setFilteredData(sortedData)
166
- setSortOrder(sortOrder === 1 ? -1 : 1)
167
- }
168
-
169
- const isNextPage = (index: number) => {
170
- if (hasPagination && itemsPerPage && !hasActiveFilter) {
171
- const currentPage = Math.ceil((index + 1) / itemsPerPage)
172
-
173
- return currentPage !== page ? 'true' : undefined
174
- }
175
-
176
- if (hasActiveFilter && itemsPerPage) {
177
- return index >= itemsPerPage ? 'true' : undefined
178
- }
179
-
180
- return undefined
181
- }
182
-
183
- return (
184
- <section className={className} id={id}>
185
- {(!!columnFilterIndexes?.length || showColumnToggle) && (
186
- <div className={styles.filters}>
187
- {!!columnFilterIndexes?.length && (
188
- <Input
189
- type="search"
190
- placeholder={filterPlaceholder}
191
- onInput={filter}
192
- >
193
- {showFilterIcon && (
194
- <span dangerouslySetInnerHTML={{ __html: searchIcon }} />
195
- )}
196
- </Input>
197
- )}
198
- {showColumnToggle && (
199
- <Select
200
- name={`data-table-${id || crypto.randomUUID()}`}
201
- itemGroups={columnToggleItems}
202
- position="bottom-end"
203
- value={columnToggleLabel}
204
- onChange={toggleColumns}
205
- updateValue={false}
206
- />
207
- )}
208
- </div>
209
- )}
210
-
211
- <div className={classes} style={styleVariables}>
212
- <table>
213
- {!!filteredHeadings?.length && (
214
- <thead>
215
- <tr>
216
- {filteredHeadings?.map((heading: HeadingObject | string, index: number) => {
217
- if (!heading) {
218
- return null
219
- }
220
-
221
- return (
222
- <th key={index}>
223
- <ConditionalWrapper
224
- condition={!!(heading as HeadingObject).sortable}
225
- wrapper={children => (
226
- <Button theme="flat" slot="wrapper" onClick={() => sort(index)}>
227
- {children}
228
- <span dangerouslySetInnerHTML={{ __html: orderIcon }} />
229
- </Button>
230
- )}
231
- >
232
- {(heading as HeadingObject).name || heading as string}
233
- </ConditionalWrapper>
234
- </th>
235
- )
236
- })}
237
- </tr>
238
- </thead>
239
- )}
240
-
241
- <tbody>
242
- {filteredData?.map((row: string[], rowIndex: number) => (
243
- <tr key={rowIndex} data-hidden={isNextPage(rowIndex)}>
244
- {row.filter(Boolean).map((column, columnIndex) => (
245
- <td
246
- key={columnIndex}
247
- dangerouslySetInnerHTML={{ __html: column }}
248
- />
249
- ))}
250
- </tr>
251
- ))}
252
- {children}
253
- </tbody>
254
- {!filteredData?.length && (
255
- <tfoot>
256
- <tr>
257
- <td
258
- colSpan={data?.[0].length}
259
- className={styles['no-results']}
260
- >
261
- {noResultsLabel}
262
- </td>
263
- </tr>
264
- </tfoot>
265
- )}
266
- </table>
267
- </div>
268
- {(subText || hasPagination) && (
269
- <div className={footerClasses}>
270
- {subText && (
271
- <span className={styles.subtext}>{subText}</span>
272
- )}
273
- {(hasPagination && itemsPerPage && !hasActiveFilter) && (
274
- <Pagination
275
- {...pagination}
276
- totalPages={Math.ceil((data?.length || 0) / itemsPerPage)}
277
- currentPage={page}
278
- onChange={event => setPage(event.page)}
279
- />
280
- )}
281
- </div>
282
- )}
283
- </section>
284
- )
285
- }
286
-
287
- export default DataTable
1
+ import React, { useState } from 'react'
2
+ import type { HeadingObject, ReactDataTableProps } from './datatable'
3
+
4
+ import Button from '../Button/Button.tsx'
5
+ import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.tsx'
6
+ import Input from '../Input/Input.tsx'
7
+ import Pagination from '../Pagination/Pagination.tsx'
8
+ import Select from '../Select/Select.tsx'
9
+
10
+ import { classNames } from '../../utils/classNames'
11
+ import { debounce } from '../../utils/debounce'
12
+
13
+ import checkIcon from '../../icons/check.svg?raw'
14
+ import orderIcon from '../../icons/order.svg?raw'
15
+ import searchIcon from '../../icons/search.svg?raw'
16
+
17
+ import styles from './datatable.module.scss'
18
+
19
+ import type { ListEventType } from '../List/list'
20
+
21
+ // eslint-disable-next-line complexity
22
+ const DataTable = ({
23
+ headings,
24
+ filterPlaceholder = 'Filter entries',
25
+ showFilterIcon,
26
+ noResultsLabel = 'No results.',
27
+ itemsPerPage,
28
+ subText,
29
+ columnToggleLabel = 'Columns',
30
+ pagination,
31
+ data,
32
+ hover,
33
+ striped,
34
+ offsetStripe,
35
+ compact,
36
+ maxHeight,
37
+ className,
38
+ id,
39
+ onFilter,
40
+ children
41
+ }: ReactDataTableProps) => {
42
+ const [filteredData, setFilteredData] = useState<any>(data)
43
+ const [toggledData, setToggledData] = useState(filteredData)
44
+ const [filteredHeadings, setFilteredHeadings] = useState<any>(headings)
45
+ const [page, setPage] = useState(1)
46
+ const [hasActiveFilter, setHasActiveFilter] = useState(false)
47
+ const [sortOrder, setSortOrder] = useState(1)
48
+
49
+ const classes = classNames([
50
+ styles.table,
51
+ hover && styles.hover,
52
+ striped && styles[`striped-${striped}s`],
53
+ offsetStripe && styles.offset,
54
+ compact && styles.compact,
55
+ maxHeight && styles.scroll
56
+ ])
57
+
58
+ const footerClasses = classNames([
59
+ styles.footer,
60
+ subText && styles.between
61
+ ])
62
+
63
+ const styleVariables = {
64
+ ...(maxHeight && { maxHeight })
65
+ } as React.CSSProperties
66
+
67
+ const showColumnToggle = headings?.some(heading => {
68
+ return typeof heading === 'string' ? false : heading.toggleable
69
+ })
70
+
71
+ const columnToggleItems = [{
72
+ items: headings?.length ? headings
73
+ .filter(heading => typeof heading !== 'string' && heading.toggleable)
74
+ .map(heading => ({
75
+ icon: checkIcon,
76
+ name: (heading as HeadingObject).name,
77
+ value: String(headings.findIndex(h => {
78
+ return (h as HeadingObject).name === (heading as HeadingObject).name
79
+ }))
80
+ })) : []
81
+ }]
82
+
83
+ const columnFilterIndexes = headings?.map(heading => (heading as HeadingObject).filterable)
84
+ .map((heading, index) => heading ? index : null)
85
+ .filter(heading => heading !== null) || []
86
+
87
+ const hasPagination = data?.length && itemsPerPage
88
+ ? data.length > itemsPerPage
89
+ : false
90
+
91
+ const filter = debounce((event: Event) => {
92
+ const target = event.target as HTMLInputElement
93
+
94
+ setHasActiveFilter(!!target.value)
95
+
96
+ setFilteredData(toggledData?.filter((row: string[]) => {
97
+ const rowValue = row.filter((_, index) => columnFilterIndexes.includes(index))
98
+ .join('')
99
+ .toLowerCase()
100
+
101
+ return rowValue.includes(target.value.toLowerCase())
102
+ }))
103
+
104
+ onFilter?.({
105
+ results: filteredData,
106
+ numberOfResults: filteredData.length
107
+ })
108
+ }, 400)
109
+
110
+ const toggleColumns = (event: ListEventType) => {
111
+ const columnToggleListElement = Array.from(event.list.children)
112
+ .find(child => (child as HTMLLIElement).dataset.name === event.name) as HTMLLIElement
113
+ const svgIcon = columnToggleListElement.children[0] as HTMLElement
114
+ let mappedData
115
+
116
+ svgIcon.style.opacity = svgIcon.style.opacity === '0'
117
+ ? '1'
118
+ : '0'
119
+
120
+ if (svgIcon.style.opacity === '0') {
121
+ mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[]) => {
122
+ return row.map((column, index) => index === Number(event.value) ? null : column)
123
+ })
124
+
125
+ setFilteredData(mappedData)
126
+
127
+ setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string) => {
128
+ return ((heading as HeadingObject)?.name || heading) === event.name ? null : heading
129
+ }))
130
+ } else {
131
+ mappedData = (hasActiveFilter ? data : filteredData)?.map((row: string[], x: number) => {
132
+ return row.map((column, y) => y === Number(event.value) ? data?.[x][y] : column)
133
+ })
134
+
135
+ setFilteredData(mappedData)
136
+
137
+ setFilteredHeadings(filteredHeadings.map((heading: HeadingObject | string, index: number) => {
138
+ return ((headings?.[index] as HeadingObject)?.name || headings?.[index]) === event.name
139
+ ? headings?.[index]
140
+ : heading
141
+ }))
142
+ }
143
+
144
+ setToggledData(mappedData)
145
+ }
146
+
147
+ const sort = (index: number) => {
148
+ const sortedData = filteredData.sort((a: string[], b: string[]) => {
149
+ let aValue: string | number = a[index]
150
+ let bValue: string | number = b[index]
151
+
152
+ if (!isNaN(aValue as any)) {
153
+ aValue = Number(aValue)
154
+ }
155
+
156
+ if (!isNaN(bValue as any)) {
157
+ bValue = Number(bValue)
158
+ }
159
+
160
+ return aValue > bValue
161
+ ? sortOrder * -1
162
+ : sortOrder
163
+ })
164
+
165
+ setFilteredData(sortedData)
166
+ setSortOrder(sortOrder === 1 ? -1 : 1)
167
+ }
168
+
169
+ const isNextPage = (index: number) => {
170
+ if (hasPagination && itemsPerPage && !hasActiveFilter) {
171
+ const currentPage = Math.ceil((index + 1) / itemsPerPage)
172
+
173
+ return currentPage !== page ? 'true' : undefined
174
+ }
175
+
176
+ if (hasActiveFilter && itemsPerPage) {
177
+ return index >= itemsPerPage ? 'true' : undefined
178
+ }
179
+
180
+ return undefined
181
+ }
182
+
183
+ return (
184
+ <section className={className} id={id}>
185
+ {(!!columnFilterIndexes?.length || showColumnToggle) && (
186
+ <div className={styles.filters}>
187
+ {!!columnFilterIndexes?.length && (
188
+ <Input
189
+ type="search"
190
+ placeholder={filterPlaceholder}
191
+ onInput={filter}
192
+ >
193
+ {showFilterIcon && (
194
+ <span dangerouslySetInnerHTML={{ __html: searchIcon }} />
195
+ )}
196
+ </Input>
197
+ )}
198
+ {showColumnToggle && (
199
+ <Select
200
+ name={`data-table-${id || crypto.randomUUID()}`}
201
+ itemGroups={columnToggleItems}
202
+ position="bottom-end"
203
+ value={columnToggleLabel}
204
+ onChange={toggleColumns}
205
+ updateValue={false}
206
+ />
207
+ )}
208
+ </div>
209
+ )}
210
+
211
+ <div className={classes} style={styleVariables}>
212
+ <table>
213
+ {!!filteredHeadings?.length && (
214
+ <thead>
215
+ <tr>
216
+ {filteredHeadings?.map((heading: HeadingObject | string, index: number) => {
217
+ if (!heading) {
218
+ return null
219
+ }
220
+
221
+ return (
222
+ <th key={index}>
223
+ <ConditionalWrapper
224
+ condition={!!(heading as HeadingObject).sortable}
225
+ wrapper={children => (
226
+ <Button theme="flat" slot="wrapper" onClick={() => sort(index)}>
227
+ {children}
228
+ <span dangerouslySetInnerHTML={{ __html: orderIcon }} />
229
+ </Button>
230
+ )}
231
+ >
232
+ {(heading as HeadingObject).name || heading as string}
233
+ </ConditionalWrapper>
234
+ </th>
235
+ )
236
+ })}
237
+ </tr>
238
+ </thead>
239
+ )}
240
+
241
+ <tbody>
242
+ {filteredData?.map((row: string[], rowIndex: number) => (
243
+ <tr key={rowIndex} data-hidden={isNextPage(rowIndex)}>
244
+ {row.filter(Boolean).map((column, columnIndex) => (
245
+ <td
246
+ key={columnIndex}
247
+ dangerouslySetInnerHTML={{ __html: column }}
248
+ />
249
+ ))}
250
+ </tr>
251
+ ))}
252
+ {children}
253
+ </tbody>
254
+ {!filteredData?.length && (
255
+ <tfoot>
256
+ <tr>
257
+ <td
258
+ colSpan={data?.[0].length}
259
+ className={styles['no-results']}
260
+ >
261
+ {noResultsLabel}
262
+ </td>
263
+ </tr>
264
+ </tfoot>
265
+ )}
266
+ </table>
267
+ </div>
268
+ {(subText || hasPagination) && (
269
+ <div className={footerClasses}>
270
+ {subText && (
271
+ <span className={styles.subtext}>{subText}</span>
272
+ )}
273
+ {(hasPagination && itemsPerPage && !hasActiveFilter) && (
274
+ <Pagination
275
+ {...pagination}
276
+ totalPages={Math.ceil((data?.length || 0) / itemsPerPage)}
277
+ currentPage={page}
278
+ onChange={event => setPage(event.page)}
279
+ />
280
+ )}
281
+ </div>
282
+ )}
283
+ </section>
284
+ )
285
+ }
286
+
287
+ export default DataTable
@@ -0,0 +1,91 @@
1
+ ---
2
+ import type { FooterProps } from './footer'
3
+
4
+ import ConditionalWrapper from '../ConditionalWrapper/ConditionalWrapper.astro'
5
+
6
+ import styles from './footer.module.scss'
7
+
8
+ interface Props extends FooterProps {}
9
+
10
+ const {
11
+ logo,
12
+ columns,
13
+ subText,
14
+ className,
15
+ wrapperClassName,
16
+ subTextClassName
17
+ } = Astro.props
18
+
19
+ const classes = [
20
+ styles.footer,
21
+ className
22
+ ]
23
+
24
+ const containerClasses = [
25
+ styles.container,
26
+ wrapperClassName
27
+ ]
28
+
29
+ const subTextClasses = [
30
+ styles.subtext,
31
+ subTextClassName
32
+ ]
33
+ ---
34
+
35
+ <footer class:list={classes}>
36
+ <div class:list={containerClasses}>
37
+ <ConditionalWrapper condition={!!(logo?.url || logo?.html)}>
38
+ <div slot="wrapper" class={styles.wrapper}>children</div>
39
+ {logo?.url && (
40
+ <a href="/">
41
+ <img
42
+ src={logo.url}
43
+ alt={logo.alt || 'Logo'}
44
+ width={logo.width}
45
+ height={logo.height}
46
+ loading={logo.eager ? undefined : 'lazy'}
47
+ />
48
+ </a>
49
+ )}
50
+
51
+ {logo?.html && (
52
+ <a href="/" aria-label={logo.alt || 'Logo'}>
53
+ <Fragment set:html={logo.html} />
54
+ </a>
55
+ )}
56
+
57
+ {!!columns?.length && (
58
+ <ConditionalWrapper condition={columns.length > 1}>
59
+ <div slot="wrapper" class={styles.columns}>children</div>
60
+ {columns.map(column => (
61
+ <ConditionalWrapper condition={!!column.title}>
62
+ <div slot="wrapper">children</div>
63
+ {column.title && (
64
+ <strong class={styles['column-title']}>{column.title}</strong>
65
+ )}
66
+ <ul class={styles.column}>
67
+ {column.items.map(item => (
68
+ <li>
69
+ <a
70
+ href={item.href}
71
+ target={item.target}
72
+ class:list={[item.active && styles.active]}
73
+ >
74
+ {item.name}
75
+ </a>
76
+ </li>
77
+ ))}
78
+ </ul>
79
+ </ConditionalWrapper>
80
+ ))}
81
+ </ConditionalWrapper>
82
+ )}
83
+ </ConditionalWrapper>
84
+ {(subText || Astro.slots.has('default')) && (
85
+ <div class:list={subTextClasses}>
86
+ {subText && <span set:html={subText} />}
87
+ <slot />
88
+ </div>
89
+ )}
90
+ </div>
91
+ </footer>