@santi020k/lumen-astro 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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/components/Accordion.astro +27 -0
  4. package/components/Affix.astro +41 -0
  5. package/components/Agenda.astro +19 -0
  6. package/components/Alert.astro +38 -0
  7. package/components/AlertDialog.astro +41 -0
  8. package/components/Anchor.astro +43 -0
  9. package/components/AnimatedLogo.astro +30 -0
  10. package/components/AnimatedNumber.astro +56 -0
  11. package/components/AnimatedPortrait.astro +124 -0
  12. package/components/AspectRatio.astro +39 -0
  13. package/components/Attachment.astro +46 -0
  14. package/components/Autocomplete.astro +24 -0
  15. package/components/Avatar.astro +36 -0
  16. package/components/BackToTop.astro +30 -0
  17. package/components/Badge.astro +36 -0
  18. package/components/Breadcrumb.astro +28 -0
  19. package/components/Bubble.astro +36 -0
  20. package/components/Button.astro +52 -0
  21. package/components/ButtonGroup.astro +27 -0
  22. package/components/ButtonLink.astro +58 -0
  23. package/components/Calendar.astro +197 -0
  24. package/components/Callout.astro +27 -0
  25. package/components/Card.astro +40 -0
  26. package/components/Carousel.astro +33 -0
  27. package/components/Cascader.astro +109 -0
  28. package/components/Chart.astro +32 -0
  29. package/components/Checkbox.astro +22 -0
  30. package/components/Code.astro +96 -0
  31. package/components/Collapsible.astro +28 -0
  32. package/components/ColorPicker.astro +16 -0
  33. package/components/Combobox.astro +67 -0
  34. package/components/Command.astro +33 -0
  35. package/components/ContextMenu.astro +44 -0
  36. package/components/CoverImage.astro +180 -0
  37. package/components/DataTable.astro +122 -0
  38. package/components/DatePicker.astro +108 -0
  39. package/components/DateRangePicker.astro +18 -0
  40. package/components/Descriptions.astro +41 -0
  41. package/components/Dialog.astro +40 -0
  42. package/components/Direction.astro +30 -0
  43. package/components/Drawer.astro +40 -0
  44. package/components/DropdownMenu.astro +40 -0
  45. package/components/Empty.astro +32 -0
  46. package/components/Eyebrow.astro +27 -0
  47. package/components/Field.astro +42 -0
  48. package/components/FileUpload.astro +44 -0
  49. package/components/FloatingBadge.astro +27 -0
  50. package/components/FormattedDate.astro +27 -0
  51. package/components/GradientDivider.astro +24 -0
  52. package/components/HoverCard.astro +40 -0
  53. package/components/Icon.astro +50 -0
  54. package/components/Image.astro +33 -0
  55. package/components/Input.astro +24 -0
  56. package/components/InputGroup.astro +27 -0
  57. package/components/InputOTP.astro +60 -0
  58. package/components/Item.astro +32 -0
  59. package/components/Kbd.astro +27 -0
  60. package/components/Label.astro +18 -0
  61. package/components/LanguageToggle.astro +30 -0
  62. package/components/Link.astro +27 -0
  63. package/components/Marker.astro +33 -0
  64. package/components/Mentions.astro +47 -0
  65. package/components/Menubar.astro +40 -0
  66. package/components/Message.astro +32 -0
  67. package/components/MessageScroller.astro +32 -0
  68. package/components/Meter.astro +31 -0
  69. package/components/NativeSelect.astro +37 -0
  70. package/components/NavigationMenu.astro +40 -0
  71. package/components/Note.astro +23 -0
  72. package/components/NumberField.astro +16 -0
  73. package/components/Pagination.astro +28 -0
  74. package/components/Particles.astro +31 -0
  75. package/components/PhoneInput.astro +86 -0
  76. package/components/Pill.astro +30 -0
  77. package/components/Popconfirm.astro +40 -0
  78. package/components/Popover.astro +40 -0
  79. package/components/Progress.astro +30 -0
  80. package/components/Prose.astro +27 -0
  81. package/components/QRCode.astro +44 -0
  82. package/components/RadioGroup.astro +28 -0
  83. package/components/Rating.astro +42 -0
  84. package/components/Resizable.astro +55 -0
  85. package/components/RevealGroup.astro +54 -0
  86. package/components/RichTextEditor.astro +19 -0
  87. package/components/Schedule.astro +19 -0
  88. package/components/ScrollArea.astro +32 -0
  89. package/components/ScrollReveal.astro +50 -0
  90. package/components/SearchField.astro +16 -0
  91. package/components/Segmented.astro +56 -0
  92. package/components/Select.astro +120 -0
  93. package/components/Separator.astro +31 -0
  94. package/components/Sheet.astro +40 -0
  95. package/components/Sidebar.astro +39 -0
  96. package/components/Skeleton.astro +36 -0
  97. package/components/SkipLink.astro +27 -0
  98. package/components/Slider.astro +20 -0
  99. package/components/Sonner.astro +38 -0
  100. package/components/SpeedDial.astro +77 -0
  101. package/components/Spinner.astro +36 -0
  102. package/components/Stat.astro +34 -0
  103. package/components/Stepper.astro +57 -0
  104. package/components/Switch.astro +23 -0
  105. package/components/Table.astro +32 -0
  106. package/components/Tabs.astro +33 -0
  107. package/components/TagGroup.astro +18 -0
  108. package/components/Textarea.astro +17 -0
  109. package/components/ThemeBuilder.astro +19 -0
  110. package/components/ThemeToggle.astro +41 -0
  111. package/components/TimeField.astro +16 -0
  112. package/components/Timeline.astro +12 -0
  113. package/components/TimelineItem.astro +19 -0
  114. package/components/Toast.astro +50 -0
  115. package/components/Toggle.astro +25 -0
  116. package/components/ToggleGroup.astro +28 -0
  117. package/components/Toolbar.astro +36 -0
  118. package/components/Tooltip.astro +27 -0
  119. package/components/Tour.astro +52 -0
  120. package/components/Transfer.astro +70 -0
  121. package/components/Tree.astro +19 -0
  122. package/components/TreeGrid.astro +19 -0
  123. package/components/TreeSelect.astro +69 -0
  124. package/components/Typography.astro +27 -0
  125. package/components/VirtualList.astro +19 -0
  126. package/components/Watermark.astro +60 -0
  127. package/index.d.ts +125 -0
  128. package/index.ts +125 -0
  129. package/package.json +74 -0
  130. package/runtime/UIPrimitives.astro +4649 -0
  131. package/styles/lumen.css +5 -0
@@ -0,0 +1,109 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface CascaderOption {
7
+ children?: CascaderOption[]
8
+ label: string
9
+ value: string
10
+ }
11
+
12
+ export interface Props extends HTMLAttributes<'div'> {
13
+ class?: string
14
+ className?: string
15
+ name?: string
16
+ options?: CascaderOption[]
17
+ placeholder?: string
18
+ }
19
+
20
+ const {
21
+ class: classProp = '',
22
+ className = '',
23
+ name,
24
+ options = [],
25
+ placeholder = 'Select…',
26
+ ...rest
27
+ } = Astro.props
28
+
29
+ interface RenderedColumn {
30
+ id: string
31
+ options: { label: string; nextId?: string | undefined; value: string }[]
32
+ root: boolean
33
+ }
34
+
35
+ const cascaderId = `ui-cascader-${Math.random().toString(36).slice(2)}`
36
+ const panelId = `${cascaderId}-panel`
37
+ let columnCount = 0
38
+ const columns: RenderedColumn[] = []
39
+
40
+ const buildColumn = (items: CascaderOption[], root: boolean): string => {
41
+ columnCount += 1
42
+
43
+ const id = `${cascaderId}-column-${columnCount}`
44
+
45
+ const rendered: RenderedColumn['options'] = items.map(item => ({
46
+ label: item.label,
47
+ value: item.value
48
+ }))
49
+
50
+ columns.push({ id, options: rendered, root })
51
+
52
+ for (const [index, item] of items.entries()) {
53
+ const renderedOption = rendered[index]
54
+
55
+ if (item.children && item.children.length > 0 && renderedOption) {
56
+ renderedOption.nextId = buildColumn(item.children, false)
57
+ }
58
+ }
59
+
60
+ return id
61
+ }
62
+
63
+ if (options.length > 0) {
64
+ buildColumn(options, true)
65
+ }
66
+
67
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
68
+ 'ui-cascader'
69
+ ], classProp, className)
70
+ ---
71
+
72
+ <div class:list={classList} data-surface="default" data-ui-cascader data-ui-popover {...passthrough}>
73
+ <button
74
+ aria-controls={panelId}
75
+ aria-expanded="false"
76
+ aria-haspopup="listbox"
77
+ class="ui-select ui-select__trigger ui-cascader__trigger"
78
+ data-ui-trigger
79
+ role="combobox"
80
+ type="button"
81
+ >
82
+ <span data-ui-cascader-placeholder={placeholder} data-ui-cascader-value>{placeholder}</span>
83
+ </button>
84
+ <div class="ui-cascader__panel" data-ui-panel hidden id={panelId}>
85
+ {columns.map(column => (
86
+ <ol aria-label={column.root ? 'Options' : 'Sub-options'} class="ui-cascader__column" hidden={!column.root} id={column.id} role="listbox">
87
+ {column.options.map(option => (
88
+ <li role="presentation">
89
+ <button
90
+ aria-selected="false"
91
+ class="ui-cascader__option"
92
+ data-label={option.label}
93
+ data-ui-cascader-next={option.nextId ? `#${option.nextId}` : undefined}
94
+ data-ui-cascader-option
95
+ data-value={option.value}
96
+ role="option"
97
+ type="button"
98
+ >
99
+ <span>{option.label}</span>
100
+ {option.nextId && <span aria-hidden="true">&rsaquo;</span>}
101
+ </button>
102
+ </li>
103
+ ))}
104
+ </ol>
105
+ ))}
106
+ <slot />
107
+ </div>
108
+ {name && <input data-ui-cascader-input name={name} type="hidden" />}
109
+ </div>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface Props extends HTMLAttributes<'figure'> {
7
+ class?: string
8
+ className?: string
9
+ glass?: boolean | 'subtle' | 'strong'
10
+ }
11
+
12
+ const {
13
+ class: classProp = '',
14
+ className = '',
15
+ glass = false,
16
+ ...rest
17
+ } = Astro.props
18
+
19
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
20
+ 'ui-chart',
21
+ glass && 'ui-chart--glass',
22
+ glass === 'subtle' && 'ui-glass-subtle',
23
+ glass === 'strong' && 'ui-glass-strong'
24
+ ], classProp, className)
25
+ ---
26
+
27
+ <figure
28
+ class:list={classList}
29
+ {...passthrough}
30
+ >
31
+ <slot />
32
+ </figure>
@@ -0,0 +1,22 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ type HTMLInputAttributes = HTMLAttributes<'input'>
7
+
8
+ interface Props extends Omit<HTMLInputAttributes, 'type'> {
9
+ class?: string
10
+ className?: string
11
+ }
12
+
13
+ const { class: classProp = '', className = '', ...rest } = Astro.props
14
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-checkbox'
15
+ ], classProp, className)
16
+ ---
17
+
18
+ <input
19
+ class:list={classList}
20
+ type="checkbox"
21
+ {...passthrough}
22
+ />
@@ -0,0 +1,96 @@
1
+ ---
2
+ import { renderLumenCodeHtml, renderLumenIconSvg, resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ type CodeTheme = 'auto' | 'lumen' | 'santi020k'
7
+ type CodeVariant = 'block' | 'inline'
8
+
9
+ interface Props extends HTMLAttributes<'code'> {
10
+ class?: string
11
+ className?: string
12
+ code?: string
13
+ copy?: boolean
14
+ highlighted?: boolean
15
+ label?: string
16
+ language?: string
17
+ theme?: CodeTheme
18
+ variant?: CodeVariant
19
+ }
20
+
21
+ const {
22
+ class: classProp = '',
23
+ className = '',
24
+ code,
25
+ copy = false,
26
+ highlighted = false,
27
+ label,
28
+ language,
29
+ theme = 'auto',
30
+ variant = 'inline',
31
+ ...rest
32
+ } = Astro.props
33
+
34
+ const { classList: blockClassList, passthrough } = resolveLumenAstroProps(rest, [
35
+ 'ui-code',
36
+ 'ui-code--block'
37
+ ], classProp, className)
38
+ const { classList: inlineClassList } = resolveLumenAstroProps(rest, [
39
+ 'ui-code',
40
+ 'ui-code--inline'
41
+ ], classProp, className)
42
+ const shouldShowHeader = variant === 'block' && Boolean(copy || label || language)
43
+ const hasCode = code !== undefined
44
+ const renderedCode = hasCode ? renderLumenCodeHtml(code, language) : ''
45
+ ---
46
+
47
+ {variant === 'block' ? (
48
+ <figure
49
+ class:list={blockClassList}
50
+ data-code-theme={theme}
51
+ data-language={language}
52
+ data-ui-code
53
+ {...passthrough}
54
+ >
55
+ {shouldShowHeader && (
56
+ <figcaption class="ui-code__header">
57
+ <span class="ui-code__dots" aria-hidden="true">
58
+ <span class="ui-code__dot ui-code__dot--red"></span>
59
+ <span class="ui-code__dot ui-code__dot--yellow"></span>
60
+ <span class="ui-code__dot ui-code__dot--green"></span>
61
+ </span>
62
+ <span class="ui-code__meta">
63
+ {language && <span class="ui-code__language">{language}</span>}
64
+ {label && <span class="ui-code__label">{label}</span>}
65
+ </span>
66
+ {copy && (
67
+ <button
68
+ aria-label="Copy code to clipboard"
69
+ class="ui-code__copy"
70
+ data-ui-code-copy
71
+ type="button"
72
+ >
73
+ <Fragment set:html={renderLumenIconSvg('copy', { className: 'ui-code__copy-icon' })} />
74
+ <Fragment set:html={renderLumenIconSvg('check', { className: 'ui-code__check-icon' })} />
75
+ </button>
76
+ )}
77
+ </figcaption>
78
+ )}
79
+ {highlighted ? (
80
+ <slot />
81
+ ) : hasCode ? (
82
+ <pre><code set:html={renderedCode}></code></pre>
83
+ ) : (
84
+ <pre><code><slot /></code></pre>
85
+ )}
86
+ </figure>
87
+ ) : (
88
+ <code
89
+ class:list={inlineClassList}
90
+ data-code-theme={theme}
91
+ data-language={language}
92
+ {...passthrough}
93
+ >
94
+ {hasCode ? code : <slot />}
95
+ </code>
96
+ )}
@@ -0,0 +1,28 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface Props extends HTMLAttributes<'details'> {
7
+ class?: string
8
+ className?: string
9
+ }
10
+
11
+ const {
12
+ class: classProp = '',
13
+ className = '',
14
+ ...rest
15
+ } = Astro.props
16
+
17
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
18
+ 'ui-collapsible'
19
+ ], classProp, className)
20
+ ---
21
+
22
+ <details
23
+ data-ui-collapsible
24
+ class:list={classList}
25
+ {...passthrough}
26
+ >
27
+ <slot />
28
+ </details>
@@ -0,0 +1,16 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface Props extends HTMLAttributes<'input'> {
7
+ class?: string
8
+ className?: string
9
+ }
10
+
11
+ const { class: classProp = '', className = '', type = 'color', ...rest } = Astro.props
12
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-color-picker'
13
+ ], classProp, className)
14
+ ---
15
+
16
+ <input class:list={classList} type={type} {...passthrough} />
@@ -0,0 +1,67 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface Props extends HTMLAttributes<'input'> {
7
+ class?: string
8
+ className?: string
9
+ label?: string
10
+ list: string
11
+ options?: ComboboxOption[]
12
+ }
13
+
14
+ interface Option {
15
+ disabled?: boolean
16
+ label: string
17
+ value: string
18
+ }
19
+
20
+ type ComboboxOption = Option | string
21
+
22
+ const {
23
+ class: classProp = '',
24
+ className = '',
25
+ id,
26
+ label,
27
+ list,
28
+ options = [],
29
+ type = 'text',
30
+ ...rest
31
+ } = Astro.props
32
+
33
+ const inputId = id ?? `${list}-input`
34
+ const normalizedOptions = options.map(option =>
35
+ typeof option === 'string' ? { label: option, value: option } : option
36
+ )
37
+ const { classList, passthrough } = resolveLumenAstroProps(rest, ['ui-combobox'
38
+ ], classProp, className)
39
+ ---
40
+
41
+ <div class:list={classList} data-ui-combobox>
42
+ {label && <label class="ui-label" for={inputId}>{label}</label>}
43
+ <input
44
+ aria-autocomplete="list"
45
+ aria-controls={list}
46
+ aria-expanded="false"
47
+ class="ui-input"
48
+ id={inputId}
49
+ role="combobox"
50
+ type={type}
51
+ {...passthrough}
52
+ />
53
+ <div class="ui-combobox__list" hidden id={list} role="listbox">
54
+ {normalizedOptions.map(option => (
55
+ <button
56
+ aria-disabled={option.disabled ? 'true' : undefined}
57
+ data-ui-combobox-option
58
+ data-value={option.value}
59
+ disabled={option.disabled}
60
+ role="option"
61
+ type="button"
62
+ >
63
+ {option.label}
64
+ </button>
65
+ ))}
66
+ </div>
67
+ </div>
@@ -0,0 +1,33 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface Props extends HTMLAttributes<'div'> {
7
+ class?: string
8
+ className?: string
9
+ glass?: boolean | 'subtle' | 'strong'
10
+ }
11
+
12
+ const {
13
+ class: classProp = '',
14
+ className = '',
15
+ glass = false,
16
+ ...rest
17
+ } = Astro.props
18
+
19
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
20
+ 'ui-command',
21
+ glass && 'ui-command--glass',
22
+ glass === 'subtle' && 'ui-glass-subtle',
23
+ glass === 'strong' && 'ui-glass-strong'
24
+ ], classProp, className)
25
+ ---
26
+
27
+ <div
28
+ data-ui-command
29
+ class:list={classList}
30
+ {...passthrough}
31
+ >
32
+ <slot />
33
+ </div>
@@ -0,0 +1,44 @@
1
+ ---
2
+ import { resolveLumenAstroProps, resolveLumenGlass } from '@santi020k/lumen-core'
3
+
4
+ import type { HTMLAttributes } from 'astro/types'
5
+
6
+ interface Props extends HTMLAttributes<'menu'> {
7
+ class?: string
8
+ className?: string
9
+ glass?: boolean | 'subtle' | 'strong'
10
+ /**
11
+ * @deprecated Use `glass` instead. `surface="glass"` maps to `glass={true}`.
12
+ */
13
+ surface?: 'default' | 'glass'
14
+ }
15
+
16
+ const {
17
+ class: classProp = '',
18
+ className = '',
19
+ glass = false,
20
+ surface = 'default',
21
+ ...rest
22
+ } = Astro.props
23
+
24
+ const resolvedGlass = resolveLumenGlass(glass, surface)
25
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
26
+ 'ui-menu',
27
+ resolvedGlass && 'ui-menu--glass',
28
+ glass === 'subtle' && 'ui-glass-subtle',
29
+ glass === 'strong' && 'ui-glass-strong'
30
+ ], classProp, className)
31
+ ---
32
+
33
+ {/* Point an element at this menu with data-ui-context-menu-trigger="<menu id>"
34
+ to get right-click / Shift+F10 open behavior from the runtime. Without a
35
+ trigger the menu renders statically. */}
36
+ <menu
37
+ data-surface={resolvedGlass ? 'glass' : 'default'}
38
+ data-ui-context-menu
39
+ role="menu"
40
+ class:list={classList}
41
+ {...passthrough}
42
+ >
43
+ <slot />
44
+ </menu>
@@ -0,0 +1,180 @@
1
+ ---
2
+ import { resolveLumenAstroProps } from '@santi020k/lumen-core'
3
+ import type { ImageMetadata } from 'astro'
4
+ import { Picture } from 'astro:assets'
5
+
6
+ import type { HTMLAttributes } from 'astro/types'
7
+
8
+ interface Props extends HTMLAttributes<'div'> {
9
+ alt: string
10
+ ariaHidden?: boolean
11
+ class?: string
12
+ className?: string
13
+ featured?: boolean
14
+ featuredMinHeightClass?: string
15
+ fetchpriority?: 'auto' | 'high' | 'low'
16
+ hover?: boolean
17
+ image: ImageMetadata
18
+ loading?: 'eager' | 'lazy'
19
+ mode?: 'card' | 'detail'
20
+ showBottomGradient?: boolean
21
+ showCardOverlay?: boolean
22
+ sizes?: string
23
+ transitionName?: string
24
+ }
25
+
26
+ const {
27
+ alt,
28
+ ariaHidden = false,
29
+ class: classProp = '',
30
+ className = '',
31
+ featured = false,
32
+ featuredMinHeightClass = 'lg:min-h-88',
33
+ fetchpriority = 'auto',
34
+ hover = false,
35
+ image,
36
+ loading = 'lazy',
37
+ mode = 'card',
38
+ showBottomGradient = false,
39
+ showCardOverlay = true,
40
+ sizes = '(min-width: 1024px) 800px, 100vw',
41
+ transitionName,
42
+ ...rest
43
+ } = Astro.props
44
+
45
+ const effectiveTransitionName = transitionName ?? `cover:${image.src}`
46
+ const aspectRatio = image.width / image.height
47
+ let orientation: 'landscape' | 'portrait' | 'square' = 'landscape'
48
+
49
+ if (aspectRatio < 0.92) {
50
+ orientation = 'portrait'
51
+ } else if (aspectRatio <= 1.08) {
52
+ orientation = 'square'
53
+ }
54
+
55
+ const useContainedFrame = orientation !== 'landscape'
56
+ let cardAspectClass = 'aspect-16/10'
57
+
58
+ if (orientation === 'portrait') {
59
+ cardAspectClass = 'aspect-4/5'
60
+ } else if (orientation === 'square') {
61
+ cardAspectClass = 'aspect-square'
62
+ }
63
+
64
+ let cardWrapperClass = `${cardAspectClass} rounded-t-[1.75rem] border-b border-line/45`
65
+
66
+ if (featured && orientation === 'landscape') {
67
+ cardWrapperClass = `aspect-16/10 rounded-t-[1.75rem] border-b border-line/45`
68
+ } else if (useContainedFrame) {
69
+ cardWrapperClass = `${cardAspectClass} rounded-t-[1.75rem] flex items-center justify-center border-b border-line/45 bg-linear-to-br from-surface-muted via-surface to-brand-soft/30 p-4 sm:p-5`
70
+ }
71
+
72
+ let detailWrapperClass = 'w-full'
73
+ let detailImageClass = 'size-full object-contain'
74
+ let detailWrapperStyle: string | undefined
75
+
76
+ let cardImageClass = `size-full object-cover motion-safe:group-hover:scale-[1.03]`
77
+
78
+ if (useContainedFrame) {
79
+ detailWrapperClass = `flex min-h-[22rem] items-center justify-center bg-linear-to-br from-surface-muted via-surface to-brand-soft/35 p-4 sm:min-h-[28rem] sm:p-6 lg:min-h-[34rem] lg:p-8`
80
+
81
+ if (orientation === 'portrait') {
82
+ detailImageClass = 'mx-auto h-full w-auto max-w-full object-contain'
83
+ } else {
84
+ detailImageClass = 'mx-auto h-full w-full max-w-[34rem] object-contain'
85
+ }
86
+
87
+ cardImageClass = `mx-auto h-full w-full object-contain motion-safe:group-hover:scale-[1.02]`
88
+ } else {
89
+ detailWrapperStyle = `aspect-ratio: ${image.width} / ${image.height};`
90
+ }
91
+
92
+ const wrapperClasses =
93
+ mode === 'card' ?
94
+ [
95
+ 'relative overflow-hidden bg-surface-muted',
96
+ cardWrapperClass,
97
+ featured && orientation === 'landscape' && featuredMinHeightClass
98
+ ] :
99
+ [
100
+ 'relative overflow-hidden bg-surface-muted',
101
+ detailWrapperClass
102
+ ]
103
+
104
+ const imageClasses = ['relative z-10 transition duration-500 motion-reduce:transition-none']
105
+
106
+ if (mode === 'detail') {
107
+ imageClasses.push(detailImageClass)
108
+ } else {
109
+ imageClasses.push(cardImageClass)
110
+ }
111
+
112
+ let targetWidth = 1280
113
+ let targetHeight = 800
114
+
115
+ if (mode === 'detail') {
116
+ targetWidth = 1600
117
+ targetHeight = 1000
118
+ }
119
+
120
+ if (orientation === 'portrait') {
121
+ targetHeight = mode === 'detail' ? 1500 : 1280
122
+ targetWidth = Math.round(targetHeight * 0.8)
123
+ } else if (orientation === 'square') {
124
+ targetHeight = mode === 'detail' ? 1200 : 1024
125
+ targetWidth = targetHeight
126
+ }
127
+
128
+ const widths =
129
+ mode === 'detail' ?
130
+ [400, 800, 1200, 1600] :
131
+ [320, 400, 560, 740, 960, 1280]
132
+
133
+ const { classList, passthrough } = resolveLumenAstroProps(rest, [
134
+ 'ui-cover-image',
135
+ hover && 'ui-cover-image--hover',
136
+ ...wrapperClasses
137
+ ], classProp, className)
138
+ ---
139
+
140
+ <div
141
+ class:list={classList}
142
+ style={mode === 'detail' ? detailWrapperStyle : undefined}
143
+ transition:name={effectiveTransitionName}
144
+ {...passthrough}
145
+ >
146
+ {mode === 'detail' && useContainedFrame && (
147
+ <div
148
+ aria-hidden="true"
149
+ class="grid-fade absolute inset-0 opacity-45"
150
+ />
151
+ )}
152
+
153
+ <Picture
154
+ alt={ariaHidden ? '' : alt}
155
+ aria-hidden={ariaHidden ? 'true' : undefined}
156
+ class:list={imageClasses}
157
+ fetchpriority={fetchpriority}
158
+ formats={['avif', 'webp']}
159
+ height={targetHeight}
160
+ loading={loading}
161
+ sizes={sizes}
162
+ src={image}
163
+ width={targetWidth}
164
+ widths={widths}
165
+ />
166
+
167
+ {mode === 'card' && !useContainedFrame && showCardOverlay && (
168
+ <div
169
+ aria-hidden="true"
170
+ class="absolute inset-0 bg-linear-to-t from-canvas/55 via-canvas/10 to-transparent"
171
+ />
172
+ )}
173
+
174
+ {showBottomGradient && (
175
+ <div
176
+ aria-hidden="true"
177
+ class="ui-cover-image__bottom-gradient"
178
+ />
179
+ )}
180
+ </div>