@studiocms/ui 0.0.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +564 -0
  3. package/package.json +49 -0
  4. package/src/components/BaseHead.astro +22 -0
  5. package/src/components/Button.astro +338 -0
  6. package/src/components/Card.astro +62 -0
  7. package/src/components/Center.astro +16 -0
  8. package/src/components/Checkbox.astro +180 -0
  9. package/src/components/Divider.astro +39 -0
  10. package/src/components/Dropdown/Dropdown.astro +253 -0
  11. package/src/components/Dropdown/dropdown.ts +170 -0
  12. package/src/components/Dropdown/index.ts +2 -0
  13. package/src/components/Input.astro +93 -0
  14. package/src/components/Modal/Modal.astro +164 -0
  15. package/src/components/Modal/index.ts +2 -0
  16. package/src/components/Modal/modal.ts +129 -0
  17. package/src/components/RadioGroup.astro +175 -0
  18. package/src/components/Row.astro +38 -0
  19. package/src/components/SearchSelect.astro +430 -0
  20. package/src/components/Select.astro +334 -0
  21. package/src/components/Sidebar/Double.astro +91 -0
  22. package/src/components/Sidebar/Single.astro +42 -0
  23. package/src/components/Sidebar/helpers.ts +133 -0
  24. package/src/components/Sidebar/index.ts +3 -0
  25. package/src/components/Textarea.astro +102 -0
  26. package/src/components/ThemeToggle.astro +40 -0
  27. package/src/components/Toast/Toaster.astro +330 -0
  28. package/src/components/Toast/index.ts +2 -0
  29. package/src/components/Toast/toast.ts +16 -0
  30. package/src/components/Toggle.astro +146 -0
  31. package/src/components/User.astro +68 -0
  32. package/src/components/index.ts +25 -0
  33. package/src/components.ts +24 -0
  34. package/src/css/colors.css +106 -0
  35. package/src/css/global.css +2 -0
  36. package/src/css/resets.css +55 -0
  37. package/src/env.d.ts +15 -0
  38. package/src/icons/Checkmark.astro +13 -0
  39. package/src/icons/ChevronUpDown.astro +13 -0
  40. package/src/icons/User.astro +13 -0
  41. package/src/icons/X-Mark.astro +13 -0
  42. package/src/layouts/RootLayout.astro +34 -0
  43. package/src/layouts/index.ts +2 -0
  44. package/src/layouts.ts +1 -0
  45. package/src/types/index.ts +11 -0
  46. package/src/utils/Icon.astro +41 -0
  47. package/src/utils/ThemeHelper.ts +127 -0
  48. package/src/utils/colors.ts +1 -0
  49. package/src/utils/generateID.ts +5 -0
  50. package/src/utils/headers.ts +190 -0
  51. package/src/utils/iconStrings.ts +29 -0
  52. package/src/utils/iconType.ts +3 -0
  53. package/src/utils/index.ts +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 StudioCMS - Louis Escher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,564 @@
1
+ # @studiocms/ui
2
+
3
+ This is the UI library that we use to build StudioCMS.
4
+
5
+ All of these components can be tested [here](https://ui-testing.studiocms.dev).
6
+
7
+ ## Components & how to use them
8
+ All components are exported from `@studiocms/ui/components`.
9
+
10
+ ### Button
11
+ A simple button component. Use it like this:
12
+
13
+ ```html
14
+ <Button>Hello World!</Button>
15
+ ```
16
+
17
+ You can pass the following props:
18
+ ```ts
19
+ type Props = {
20
+ as?: As; // What the button should be rendered as. Set to "a" for an anchor tag, etc.
21
+ size?: 'sm' | 'md' | 'lg';
22
+ fullWidth?: boolean; // Make the button take up full width
23
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger';
24
+ variant?: 'solid' | 'outlined' | 'flat';
25
+ class?: string;
26
+ disabled?: boolean;
27
+ // Note: you can pass all other HTML attributes and they will be applied.
28
+ };
29
+ ```
30
+
31
+ ### Card
32
+ A simple card component with support for a header and footer. Use it like this:
33
+ ```html
34
+ <Card>
35
+ <div slot="header">Header Content</div>
36
+ <div>Body Content</div> <!-- No slot definition needed for the body! -->
37
+ <div slot="footer">Footer Content</div>
38
+ </Card>
39
+ ```
40
+
41
+ You can pass the following props:
42
+ ```ts
43
+ type Props = {
44
+ as?: As;
45
+ class?: string;
46
+ fullWidth?: boolean;
47
+ fullHeight?: boolean;
48
+ };
49
+ ```
50
+
51
+ ### Center
52
+ A component that automatically centers all of it's content. Use it like this:
53
+ ```html
54
+ <Center>Content in here will be centered!</Center>
55
+ ```
56
+
57
+ ### Checkbox
58
+ A checkbox component. Use it like this:
59
+ ```html
60
+ <Checkbox
61
+ label="Label"
62
+ />
63
+ ```
64
+
65
+ You can pass the following props:
66
+ ```ts
67
+ type Props = {
68
+ label: string;
69
+ size?: 'sm' | 'md' | 'lg';
70
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger';
71
+ defaultChecked?: boolean;
72
+ disabled?: boolean;
73
+ name?: string; // If you want to use this in forms. If unset, a name will be auto-generated.
74
+ isRequired?: boolean;
75
+ };
76
+ ```
77
+
78
+ ### Divider
79
+ A simple divider. Use it like this:
80
+ ```html
81
+ <Divider>Divider Label</Divider>
82
+ ```
83
+
84
+ ### Dropdown
85
+ A dropdown component. Use it like this:
86
+ ```html
87
+ <Dropdown
88
+ options={[
89
+ { label: 'Option 1', value: 'opt-1' },
90
+ { label: 'Option 2', value: 'opt-2' },
91
+ ]}
92
+ id='dropdown'
93
+ >
94
+ <div slot="icon-start"><!-- Icon goes here --></div>
95
+ <div>Your Trigger goes here!</div>
96
+ <div slot="icon-end"><!-- Icon goes here --></div>
97
+ </Dropdown>
98
+ ```
99
+ **This component needs a helper to function.** Add it in a script tag:
100
+ ```html
101
+ <script>
102
+ import { DropdownHelper } from "@studiocms/ui/components";
103
+
104
+ const dropdown1 = new DropdownHelper('dropdown-1');
105
+ dropdown1.registerClickCallback((value) => {
106
+ // "value" will be what you put in the options.
107
+ // When an option is clicked, it will supply its value here
108
+ console.log(value);
109
+ });
110
+ </script>
111
+ ```
112
+ You can pass the following props:
113
+ ```ts
114
+ type Option = {
115
+ label: string;
116
+ value: string;
117
+ disabled?: boolean;
118
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger';
119
+ };
120
+
121
+ type Props = {
122
+ id: string;
123
+ options: Option[];
124
+ disabled?: boolean;
125
+ align?: 'start' | 'center' | 'end';
126
+ triggerOn?: 'left' | 'right' | 'both';
127
+ };
128
+ ```
129
+
130
+ ### Input
131
+ A text input. Use it like this:
132
+ ```html
133
+ <Input />
134
+ ```
135
+ You can pass the following props:
136
+ ```ts
137
+ type Props = {
138
+ label?: string;
139
+ type?: 'text' | 'password';
140
+ placeholder?: string;
141
+ isRequired?: boolean;
142
+ name?: string;
143
+ disabled?: boolean;
144
+ defaultValue?: string;
145
+ class?: string;
146
+ };
147
+ ```
148
+
149
+ ### Modal
150
+ A modal component with form support. Use it like this:
151
+ ```html
152
+ <Modal id="modal">
153
+ <!-- This component has a header slot! Use it like this: -->
154
+ <h2 slot="header">Header content</h2>
155
+ <div>Modal content</div>
156
+ </Modal>
157
+ <Button id="modal-trigger">Open Modal</Button>
158
+ ```
159
+ **This component needs a helper to function.** Add it in a script tag:
160
+ ```html
161
+ <script>
162
+ import { ModalHelper } from "@studiocms/ui/components";
163
+
164
+ const modal = new ModalHelper('modal');
165
+ modal.bindTrigger('modal-trigger');
166
+ </script>
167
+ ```
168
+ You can make the modal a form and also display "confirm" and "cancel" buttons individually:
169
+ ```html
170
+ <Modal id="modal" isForm buttons={['confirm', 'cancel']}>
171
+ <h2 slot="header">Header content</h2>
172
+ <div>Modal content</div>
173
+ </Modal>
174
+ <Button id="modal-trigger">Open Modal</Button>
175
+ ```
176
+ *Note: The order in which you supply the buttons does not change the buttons order in the modal.*
177
+
178
+ After adding a button, you can register a callback that will be fired when a button is clicked. If you made the modal a form, this will also give you a way to access the form data:
179
+ ```html
180
+ <script>
181
+ import { ModalHelper } from "@studiocms/ui/components";
182
+
183
+ const modal = new ModalHelper('modal');
184
+ modal.bindTrigger('modal-trigger');
185
+
186
+ // The cancel callback will not be supplied the formData, even if you made the modal a form.
187
+ modal.registerCancelCallback(() => {
188
+ // Your cancellation logic
189
+ });
190
+
191
+ // formData will be undefined unless you set the isForm prop!
192
+ modal.registerConfirmCallback((formData) => {
193
+ // Your confirmation logic
194
+ });
195
+ </script>
196
+ ```
197
+ You can pass the following props:
198
+ ```ts
199
+ type Props = {
200
+ id: string;
201
+ size?: 'sm' | 'md' | 'lg';
202
+ dismissable?: boolean;
203
+ buttons?: ('confirm' | 'cancel')[];
204
+ isForm?: boolean;
205
+ };
206
+ ```
207
+
208
+ ### RadioGroup
209
+ A radio group. Use it like this:
210
+ ```html
211
+ <RadioGroup
212
+ label="Label"
213
+ options={[
214
+ { label: "Option 1", value: "opt-1" },
215
+ { label: "Option 2", value: "opt-2" },
216
+ { label: "Option 3", value: "opt-3" }
217
+ ]}
218
+ />
219
+ ```
220
+ You can pass the following props:
221
+ ```ts
222
+ type Option = {
223
+ label: string;
224
+ value: string;
225
+ disabled?: boolean;
226
+ };
227
+
228
+ type Props = {
229
+ label: string;
230
+ options: Option[];
231
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger';
232
+ defaultValue?: string; // Needs to be the value of an option!
233
+ disabled?: boolean;
234
+ name?: string;
235
+ isRequired?: boolean;
236
+ horizontal?: boolean;
237
+ class?: string;
238
+ };
239
+ ```
240
+
241
+ ### Row
242
+ A simple row component, essentially a flex div to wrap stuff in. Use it like this:
243
+ ```html
244
+ <Row>
245
+ <Button>These two will be...</Button>
246
+ <Button>in a row!</Button>
247
+ </Row>
248
+ ```
249
+ You can pass the following props:
250
+ ```ts
251
+ type Props = {
252
+ alignCenter?: boolean;
253
+ gapSize?: 'sm' | 'md' | 'lg';
254
+ };
255
+ ```
256
+
257
+ ### Select
258
+ A select component. Use it like this:
259
+ ```html
260
+ <Select
261
+ label='Select'
262
+ options={[
263
+ { label: "Option 1", value: "opt-1" },
264
+ { label: "Option 2", value: "opt-2" },
265
+ { label: "Option 3", value: "opt-3" },
266
+ ]}
267
+ />
268
+ ```
269
+ You can pass the following props:
270
+ ```ts
271
+ type Option = {
272
+ label: string;
273
+ value: string;
274
+ disabled?: boolean;
275
+ };
276
+
277
+ type Props = {
278
+ label?: string;
279
+ defaultValue?: string;
280
+ class?: string;
281
+ name?: string;
282
+ isRequired?: boolean;
283
+ options: Option[];
284
+ disabled?: boolean;
285
+ fullWidth?: boolean;
286
+ };
287
+ ```
288
+
289
+ ### Sidebar
290
+ The sidebar comes in two flavors: single and double. **Each requires a helper and functions slightly differently.** We recommend you use these in a layout. If you do, make sure to make the sidebar's parent a flex-div with `flex-direction: row` and set it to `height: 100vh` or `height: 100dvh`. The sidebar might not work correctly otherwise.
291
+
292
+ #### Single Sidebar
293
+ The layout of your page should look similar to this:
294
+ ```html
295
+ <main>
296
+ <Sidebar>
297
+ Sidebar Content (anchor tags etc.)
298
+ </Sidebar>
299
+ <div class="content">
300
+ <!--
301
+ This will be the trigger for your sidebar.
302
+ You can use whatever you want here, like an icon.
303
+ We recommend placing this in the upper left corner of the screen.
304
+ -->
305
+ <Button id='sidebar-toggle'>
306
+ Sidebar toggle.
307
+ </Button>
308
+
309
+ <slot /> <!-- The slot for your content -->
310
+ </div>
311
+ </main>
312
+ <style>
313
+ .main {
314
+ width: 100vw;
315
+ height: 100vh;
316
+ overflow: hidden;
317
+ display: flex;
318
+ flex-direction: row;
319
+ align-items: center;
320
+ }
321
+
322
+ .content {
323
+ width: 100%;
324
+ height: 100%;
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ text-align: center;
329
+ position: relative;
330
+ }
331
+ </style>
332
+ <script>
333
+ import { SingleSidebarHelper } from "@studiocms/ui/components";
334
+
335
+ // Use the ID of your trigger here!
336
+ new SingleSidebarHelper('sidebar-toggle');
337
+ </script>
338
+ ```
339
+
340
+ We recommend you use the same button to both show and hide the sidebar, and that you hide it on devides with a screen size larger than `840px`.
341
+
342
+ If needed, it is possible to register elements to open / close the sidebar manually:
343
+ ```html
344
+ <script>
345
+ import { SingleSidebarHelper } from "@studiocms/ui/components";
346
+
347
+ const sidebar = new SingleSidebarHelper();
348
+
349
+ // Use the IDs of the respective elements here:
350
+ sidebar.hideSidebarOnClick('another-elements-id');
351
+ sidebar.showSidebarOnClick('another-elements-id');
352
+ </script>
353
+ ```
354
+ You can even just use the barebones function calls if you need to close the sidebar programatically:
355
+ ```html
356
+ <script>
357
+ import { SingleSidebarHelper } from "@studiocms/ui/components";
358
+
359
+ const sidebar = new SingleSidebarHelper();
360
+
361
+ sidebar.hideSidebar();
362
+ sidebar.showSidebar();
363
+ </script>
364
+ ```
365
+
366
+ #### Double Sidebar
367
+ When using the double sidebar, the layout of your page should look similar to this:
368
+ ```html
369
+ <main class="main">
370
+ <DoubleSidebar>
371
+ <div slot="outer" class="outer">
372
+ <Button id='to-inner'>
373
+ To Inner Sidebar
374
+ </Button>
375
+ </div>
376
+ <div slot="inner">
377
+ <Button id='to-outer'>
378
+ To Outer Sidebar
379
+ </Button>
380
+ <Button id='to-content'>
381
+ To Content
382
+ </Button>
383
+ </div>
384
+ </DoubleSidebar>
385
+ <div class="content">
386
+ <Button id='sidebar-toggle'>
387
+ Sidebar toggle.
388
+ </Button>
389
+ <slot />
390
+ </div>
391
+ </main>
392
+ <style>
393
+ .main {
394
+ width: 100vw;
395
+ height: 100vh;
396
+ overflow: hidden;
397
+ display: flex;
398
+ flex-direction: row;
399
+ align-items: center;
400
+ }
401
+
402
+ .content {
403
+ width: 100%;
404
+ height: 100%;
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: center;
408
+ text-align: center;
409
+ }
410
+ </style>
411
+ <script>
412
+ import { DoubleSidebarHelper } from "@studiocms/ui/components";
413
+
414
+ // The helper. Pass in the initial sidebar trigger id and the
415
+ // id for the button to switch from inner to outer sidebar on
416
+ // mobile devices.
417
+ const dbsb = new DoubleSidebarHelper('sidebar-toggle', 'to-outer');
418
+
419
+ // The id for the element to show the inner sidebar when clicked.
420
+ // Should be used when the user is currently on the outer sidebar
421
+ // and needs to switch to the inner.
422
+ dbsb.showInnerOnClick('to-inner');
423
+
424
+ // The id for the element to show the content on click. Should
425
+ // be used from the inner sidebar when the user wants to see the
426
+ // page content.
427
+ dbsb.showContentOnClick('to-content');
428
+ </script>
429
+ ```
430
+ The double sidebar, like the single sidebar, supports individual function calls to show and hide the outer or inner navbars programatically:
431
+
432
+ ```html
433
+ <script>
434
+ import { DoubleSidebarHelper } from "@studiocms/ui/components";
435
+
436
+ const sidebar = new DoubleSidebarHelper('sidebar-toggle', 'to-outer');
437
+
438
+ sidebar.hideSidebar();
439
+ sidebar.showInnerSidebar();
440
+ sidebar.showOuterSidebar();
441
+ </script>
442
+ ```
443
+ *Note: The API (especially for the double sidebar) is highly likely to change to make it easier to use.*
444
+
445
+ ### Textarea
446
+ A simple textarea component. Use it like this:
447
+ ```html
448
+ <Textarea />
449
+ ```
450
+ You can pass the following props:
451
+ ```ts
452
+ type Props = {
453
+ label?: string;
454
+ placeholder?: string;
455
+ isRequired?: boolean;
456
+ fullWidth?: boolean;
457
+ resize?: boolean;
458
+ name?: string;
459
+ disabled?: boolean;
460
+ defaultValue?: string;
461
+ };
462
+ ```
463
+
464
+ ### Toast
465
+ A component and helper for toasts. Use the component like this *(we recommend placing it in a layout)*:
466
+ ```html
467
+ <Toaster />
468
+ ```
469
+ You can pass the following props to the `<Toaster />` component:
470
+ ```ts
471
+ type Props = {
472
+ position?:
473
+ | 'top-left'
474
+ | 'top-right'
475
+ | 'top-center'
476
+ | 'bottom-left'
477
+ | 'bottom-right'
478
+ | 'bottom-center';
479
+ class?: string;
480
+ duration?: number;
481
+ expand?: boolean;
482
+ closeButton?: boolean;
483
+ offset?: number;
484
+ gap?: number;
485
+ };
486
+ ```
487
+ **This component needs a helper to function.** You can create toasts from a script tag:
488
+ ```html
489
+ <script>
490
+ import { toast } from '@studiocms/ui/components';
491
+
492
+ toast({
493
+ title: "Toast Title",
494
+ type: 'info',
495
+ });
496
+ </script>
497
+ ```
498
+ Only a title and a type are required to create a toast. However, you can pass the following options to customize it a little more:
499
+ ```ts
500
+ type Props = {
501
+ title: string;
502
+ type: 'success' | 'warning' | 'danger' | 'info';
503
+ description?: string;
504
+ duration?: number;
505
+ closeButton?: boolean;
506
+ persistent?: boolean; // When set to true, you MUST set "closeButton" to true as well.
507
+ };
508
+ ```
509
+
510
+ ### Toggle
511
+ A toggle component, essentially a checkbox with different design. Use it like this:
512
+ ```html
513
+ <Toggle label='Label' />
514
+ ```
515
+ You can pass the following props:
516
+ ```ts
517
+ type Props = {
518
+ label: string;
519
+ size?: 'sm' | 'md' | 'lg';
520
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger';
521
+ defaultChecked?: boolean;
522
+ disabled?: boolean;
523
+ name?: string;
524
+ isRequired?: boolean;
525
+ };
526
+ ```
527
+
528
+ ### User
529
+ A helper to display a user. Use it like this:
530
+ ```html
531
+ <User
532
+ name="Louis Escher"
533
+ description='UI/UX & Developer'
534
+ />
535
+ ```
536
+ Optionally, you can pass an avatar to be shown instead of the default placeholder, and also pass a class name for the container:
537
+ ```html
538
+ <User
539
+ name='Louis Escher'
540
+ description='UI/UX & Developer'
541
+ avatar='https://avatars.githubusercontent.com/u/66965600?v=4'
542
+ class="mt-4"
543
+ />
544
+ ```
545
+
546
+ ## Planned
547
+
548
+ ### Based on popover API
549
+ - Tooltips based on popover & anchor positioning API (not supported at time of writing, needs a custom solution)
550
+ - Date Picker
551
+
552
+ ### Loading Stuff
553
+ - Skeleton
554
+ - Spinners
555
+
556
+ ### Other Stuff
557
+ - Image dropzone
558
+
559
+ ### Otter Stuff
560
+ - Markdown editor (WYSIWYG (thanks Otter <3))
561
+
562
+ ### Maybe's
563
+ - Implement tailwind's prose
564
+ - Maybe starlight props.css
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@studiocms/ui",
3
+ "version": "0.0.1",
4
+ "description": "The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/withstudiocms/ui.git"
8
+ },
9
+ "author": {
10
+ "name": "Louis Escher",
11
+ "url": "https://studiocms.dev"
12
+ },
13
+ "contributors": [
14
+ "louisescher"
15
+ ],
16
+ "keywords": [
17
+ "astro-studiocms",
18
+ "cms",
19
+ "studiocms"
20
+ ],
21
+ "homepage": "https://ui.studiocms.dev",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "sideEffects": false,
26
+ "files": [
27
+ "src"
28
+ ],
29
+ "license": "MIT",
30
+ "type": "module",
31
+ "exports": {
32
+ "./components": "./src/components.ts",
33
+ "./layouts": "./src/layouts.ts",
34
+ "./css/*": "./src/css/*",
35
+ "./utils/*": "./src/utils/*",
36
+ "./types": "./src/types/index.ts"
37
+ },
38
+ "dependencies": {
39
+ "@fontsource-variable/onest": "5.1.0",
40
+ "@iconify/utils": "^2.1.33",
41
+ "@iconify-json/heroicons": "^1.2.1"
42
+ },
43
+ "peerDependencies": {
44
+ "astro": "^4.5 || ^5.0.0-beta.0"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "^5.7.2"
48
+ }
49
+ }
@@ -0,0 +1,22 @@
1
+ ---
2
+ import { type HeadConfig, createHead, headDefaults } from '../utils/headers';
3
+
4
+ export interface Props {
5
+ title: string;
6
+ description: string;
7
+ headers?: HeadConfig | undefined;
8
+ image?: string | undefined;
9
+ }
10
+
11
+ const { title, description, image, headers: userHeaders } = Astro.props;
12
+
13
+ const canonicalURL = Astro.site ? new URL(Astro.url.pathname, Astro.site) : undefined;
14
+
15
+ // This should probably be removed from the UI lib at some point
16
+ const defaultHeaders = headDefaults(title, description, Astro, image, canonicalURL);
17
+
18
+ const head = createHead(defaultHeaders, userHeaders || []);
19
+ ---
20
+ {head.map(({ tag: Tag, attrs, content }) => (
21
+ <Tag {...attrs} set:html={content} />
22
+ ))}