@nexxtmove/ui 1.13.0 → 1.15.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.
- package/dist/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.vue.d.ts +35 -0
- package/dist/components/molecules/TemplateCard/TemplateCard.vue.d.ts +39 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +469 -362
- package/dist/nuxt.js +4 -3
- package/package.json +8 -8
- package/src/components/atoms/Switch/Switch.vue +8 -1
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.vue +83 -0
- package/src/components/molecules/TemplateCard/TemplateCard.vue +156 -30
- package/src/components.json +2 -1
- package/src/index.ts +6 -0
- package/src/nuxt.ts +15 -2
package/dist/nuxt.js
CHANGED
|
@@ -63,7 +63,8 @@ var s = {
|
|
|
63
63
|
NexxtTimelinePhaseblock: "components/organisms/TimelinePhaseblock/TimelinePhaseblock.vue",
|
|
64
64
|
NexxtTimelineTrack: "components/organisms/TimelineTrack/TimelineTrack.vue",
|
|
65
65
|
NexxtToaster: "components/organisms/Toaster/Toaster.vue",
|
|
66
|
-
NexxtVideoPlayer: "components/organisms/VideoPlayer/VideoPlayer.vue"
|
|
66
|
+
NexxtVideoPlayer: "components/organisms/VideoPlayer/VideoPlayer.vue",
|
|
67
|
+
NexxtEditorSettingsBlock: "components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.vue"
|
|
67
68
|
}, c = ["@nexxtmove/ui/ui.css", "@nexxtmove/ui/dist/ui.css"], l = (e, t) => e.some((e) => e === t || c.includes(e)), u = i({
|
|
68
69
|
meta: {
|
|
69
70
|
name: "@nexxtmove/ui",
|
|
@@ -85,8 +86,8 @@ var s = {
|
|
|
85
86
|
});
|
|
86
87
|
let d = u("./assets");
|
|
87
88
|
if (a(d)) {
|
|
88
|
-
let e = `/${[c.options.app.buildAssetsDir, "assets"].join("/").replace(/\/+/g, "/").replace(/^\/|\/$/g, "")}
|
|
89
|
-
|
|
89
|
+
let e = `/${[c.options.app.buildAssetsDir, "assets"].join("/").replace(/\/+/g, "/").replace(/^\/|\/$/g, "")}/`, t = c.options;
|
|
90
|
+
t.nitro.publicAssets ??= [], t.nitro.publicAssets.push({
|
|
90
91
|
dir: d,
|
|
91
92
|
baseURL: e,
|
|
92
93
|
maxAge: 31536e3
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexxtmove/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.15.0",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"@eslint/js": "^10.0.1",
|
|
38
38
|
"@nuxt/kit": "^4.5.2",
|
|
39
39
|
"@nuxt/schema": "^4.5.2",
|
|
40
|
-
"@storybook/addon-a11y": "^10.
|
|
40
|
+
"@storybook/addon-a11y": "^10.6.0",
|
|
41
41
|
"@storybook/addon-designs": "^11.1.4",
|
|
42
|
-
"@storybook/addon-docs": "^10.
|
|
43
|
-
"@storybook/addon-vitest": "^10.
|
|
44
|
-
"@storybook/builder-vite": "^10.
|
|
45
|
-
"@storybook/vue3-vite": "^10.
|
|
42
|
+
"@storybook/addon-docs": "^10.6.0",
|
|
43
|
+
"@storybook/addon-vitest": "^10.6.0",
|
|
44
|
+
"@storybook/builder-vite": "^10.6.0",
|
|
45
|
+
"@storybook/vue3-vite": "^10.6.0",
|
|
46
46
|
"@tailwindcss/vite": "^4.3.3",
|
|
47
47
|
"@types/node": "^26.2.0",
|
|
48
48
|
"@types/react": "^19.2.18",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"eslint-config-prettier": "^10.1.8",
|
|
60
60
|
"eslint-plugin-better-tailwindcss": "4.7.0",
|
|
61
61
|
"eslint-plugin-prettier": "^5.5.6",
|
|
62
|
-
"eslint-plugin-storybook": "10.
|
|
62
|
+
"eslint-plugin-storybook": "10.6.0",
|
|
63
63
|
"eslint-plugin-vue": "^10.10.0",
|
|
64
64
|
"flag-icons": "^7.5.0",
|
|
65
65
|
"globals": "^17.11.0",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"react": "^19.2.8",
|
|
71
71
|
"react-dom": "^19.2.8",
|
|
72
72
|
"remark-frontmatter": "^5.0.0",
|
|
73
|
-
"storybook": "^10.
|
|
73
|
+
"storybook": "^10.6.0",
|
|
74
74
|
"typescript-eslint": "^8.67.0",
|
|
75
75
|
"vite": "^8.2.1",
|
|
76
76
|
"vite-plugin-dts": "^5.0.3",
|
|
@@ -61,11 +61,18 @@ const iconSizeClasses: Record<SwitchSize, string> = {
|
|
|
61
61
|
<template>
|
|
62
62
|
<label class="inline-flex cursor-pointer items-center gap-2">
|
|
63
63
|
<slot v-if="labelPosition === 'before'" />
|
|
64
|
-
|
|
64
|
+
<!--
|
|
65
|
+
`shrink-0`: the track has a fixed pixel size, so letting flex shrink it
|
|
66
|
+
squashes the toggle the moment a sibling label wraps onto a second line.
|
|
67
|
+
-->
|
|
68
|
+
<span :class="['relative inline-block shrink-0', trackClasses[size]]">
|
|
65
69
|
<input v-bind="$attrs" type="checkbox" class="peer sr-only" v-model="model" />
|
|
66
70
|
<span
|
|
67
71
|
:class="[
|
|
68
72
|
'absolute inset-0 overflow-hidden rounded-[30px] border transition-colors duration-200',
|
|
73
|
+
// The input is `sr-only`, so the track has to render the focus ring.
|
|
74
|
+
// `outline` draws outside the box and is not clipped by `overflow-hidden`.
|
|
75
|
+
'peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-cornflower-blue-500',
|
|
69
76
|
model ? checkedColorClasses[color] : 'border-gray-500 bg-gray-200',
|
|
70
77
|
]"
|
|
71
78
|
>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import Switch from '../../../atoms/Switch/Switch.vue'
|
|
4
|
+
|
|
5
|
+
interface NexxtEditorSettingsBlockProps {
|
|
6
|
+
/** Header text of the block. Wraps over multiple lines when it is too long. */
|
|
7
|
+
label: string
|
|
8
|
+
/**
|
|
9
|
+
* Render a switch next to the label that collapses and expands the content.
|
|
10
|
+
* When `false` the content is always visible and the model is ignored.
|
|
11
|
+
*/
|
|
12
|
+
switchable?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
defineOptions({ name: 'NexxtEditorSettingsBlock' })
|
|
16
|
+
|
|
17
|
+
const { label, switchable = false } = defineProps<NexxtEditorSettingsBlockProps>()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Open state of the block, driven by the switch. Only has an effect when
|
|
21
|
+
* `switchable` is `true`; the parent owns the state.
|
|
22
|
+
*/
|
|
23
|
+
const model = defineModel<boolean>({ default: true })
|
|
24
|
+
|
|
25
|
+
const open = computed(() => !switchable || model.value)
|
|
26
|
+
|
|
27
|
+
// Vue only drops an attribute on `false` for a fixed list of boolean
|
|
28
|
+
// attributes, and `inert` is not on that list: `:inert="false"` renders
|
|
29
|
+
// `inert="false"`, which a browser still treats as inert. `undefined` is the
|
|
30
|
+
// only value that removes the attribute again.
|
|
31
|
+
const inert = computed(() => (open.value ? undefined : true))
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="border-b border-gray-200 py-4 last:border-b-0">
|
|
36
|
+
<!--
|
|
37
|
+
Switch.vue sets `inheritAttrs: false` and binds `$attrs` on its sr-only
|
|
38
|
+
input, so a class on <Switch> never reaches its root <label>. The child
|
|
39
|
+
variant below styles that label from the header instead, which keeps the
|
|
40
|
+
whole row clickable and the accessible name on the real <label>. The gap
|
|
41
|
+
has to go on that label too: the header has a single child in both modes,
|
|
42
|
+
so its own `gap` would never apply and the spacing would silently fall
|
|
43
|
+
back to the `gap-2` that Switch.vue sets for itself.
|
|
44
|
+
-->
|
|
45
|
+
<div class="flex items-center [&>label]:flex [&>label]:w-full [&>label]:gap-3">
|
|
46
|
+
<Switch v-if="switchable" v-model="model" size="sm" color="blue" label-position="before">
|
|
47
|
+
<span class="grow text-left small-normal text-gray-900">{{ label }}</span>
|
|
48
|
+
</Switch>
|
|
49
|
+
<p v-else class="small-normal text-gray-900">{{ label }}</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
v-if="$slots.default"
|
|
54
|
+
:inert="inert"
|
|
55
|
+
:class="[
|
|
56
|
+
'grid',
|
|
57
|
+
open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
|
|
58
|
+
switchable &&
|
|
59
|
+
'transition-[grid-template-rows] duration-300 ease-in-out motion-reduce:transition-none',
|
|
60
|
+
]"
|
|
61
|
+
>
|
|
62
|
+
<!--
|
|
63
|
+
`overflow-hidden` is what makes the 0fr collapse clip, but it also clips
|
|
64
|
+
a focus ring on a full-width control in the slot, and the block has no
|
|
65
|
+
horizontal padding of its own. `-mx-1` widens the clipping box by 4px on
|
|
66
|
+
each side and the `px-1` below pushes the content back, so the ring has
|
|
67
|
+
room while the content keeps the same x position as the label.
|
|
68
|
+
-->
|
|
69
|
+
<div
|
|
70
|
+
:class="[
|
|
71
|
+
'-mx-1 min-h-0 overflow-hidden',
|
|
72
|
+
open ? 'opacity-100' : 'opacity-0',
|
|
73
|
+
switchable && 'transition-opacity duration-200 ease-in-out motion-reduce:transition-none',
|
|
74
|
+
]"
|
|
75
|
+
>
|
|
76
|
+
<!-- Padding sits on the inner element so nothing is left over at 0fr. -->
|
|
77
|
+
<div class="px-1 pt-3 pb-1">
|
|
78
|
+
<slot />
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|
|
@@ -7,11 +7,31 @@ import NexxtTooltip from '../../atoms/Tooltip/Tooltip.vue'
|
|
|
7
7
|
export type NexxtTemplateCardContentFeature = 'references' | 'valuation' | 'reviews' | 'packages'
|
|
8
8
|
|
|
9
9
|
export interface NexxtTemplateCardProps {
|
|
10
|
+
/** Preview image of the template. Falls back to a placeholder of the same height when omitted. */
|
|
10
11
|
image?: string
|
|
12
|
+
/** Template name, shown as the card heading. Truncated on one line. */
|
|
11
13
|
title: string
|
|
14
|
+
/** Secondary line under the title, e.g. when the template was last changed. Truncated on one line. */
|
|
12
15
|
subtitle?: string
|
|
16
|
+
/** Content blocks the template contains; each renders an icon with a tooltip. */
|
|
13
17
|
contentFeatures?: NexxtTemplateCardContentFeature[]
|
|
18
|
+
/** Overrides the default Dutch tooltip text per content feature. */
|
|
14
19
|
messages?: Partial<Record<NexxtTemplateCardContentFeature, string>>
|
|
20
|
+
/** Lays a full-card button over the content that emits `select`. Off keeps the card inert, so a read-only grid adds no empty tabstop per card. */
|
|
21
|
+
selectable?: boolean
|
|
22
|
+
/** Whether this card is currently chosen: draws the blue border and, with `checkmark`, the badge. Only does something with `selectable`. A prop plus the `select` emit rather than a `defineModel`, because the card owns no state: the parent grid decides which single card may be chosen. */
|
|
23
|
+
selected?: boolean
|
|
24
|
+
/** Shows a check badge in the top-right corner while selected. Only does something with `selectable`. */
|
|
25
|
+
checkmark?: boolean
|
|
26
|
+
/** Disables the select button, dims the card content and drops the overlay. Only does something with `selectable`. */
|
|
27
|
+
disabled?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type NexxtTemplateCardSlots = {
|
|
31
|
+
/** Covers the whole card and fades in on hover or keyboard focus; the content underneath dims. Not rendered while the card is selectable and disabled. On a coarse pointer it shrinks to the image band, without veil or blur, so the title stays readable and the feature icons stay tappable. The layer is click-through, but `*:pointer-events-auto` only reaches its direct children: an own `absolute inset-0` wrapper inside the slot swallows the select click. */
|
|
32
|
+
overlay?(props: { selected: boolean }): unknown
|
|
33
|
+
/** Top-right corner of the card, for a preview or menu button. Stays interactive while `disabled`, which makes it the place for e.g. a preview button: the overlay is not rendered then. */
|
|
34
|
+
actions?(props: { selected: boolean }): unknown
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
type TemplateCardIcon = {
|
|
@@ -45,46 +65,152 @@ const props = withDefaults(defineProps<NexxtTemplateCardProps>(), {
|
|
|
45
65
|
subtitle: undefined,
|
|
46
66
|
contentFeatures: () => [],
|
|
47
67
|
messages: () => ({}),
|
|
68
|
+
selectable: false,
|
|
69
|
+
selected: false,
|
|
70
|
+
checkmark: false,
|
|
71
|
+
disabled: false,
|
|
48
72
|
})
|
|
49
73
|
|
|
74
|
+
const emit = defineEmits<{
|
|
75
|
+
/** The card was chosen. No payload: the consumer knows which template it rendered. */
|
|
76
|
+
select: []
|
|
77
|
+
}>()
|
|
78
|
+
|
|
79
|
+
// The root of the template stays a plain `<div>` and must stay single-root: the heading, the
|
|
80
|
+
// paragraph and the tooltip triggers are flow content, which is invalid inside a `<button>`, and
|
|
81
|
+
// wrapping them also dropped the heading role and turned the accessible name of the button into
|
|
82
|
+
// the whole card as one long string. A leading comment in the template would make it a fragment,
|
|
83
|
+
// after which class and attributes no longer reach the card, so the notes live here.
|
|
84
|
+
// Selection, hover and the dim are driven from the props and from the root's own `:hover`, not
|
|
85
|
+
// from the button. The slot checks are read inline rather than through a computed -- slot presence
|
|
86
|
+
// is not reactive, so a consumer toggling `<template v-if>` on a slot would never re-evaluate a
|
|
87
|
+
// cached computed.
|
|
88
|
+
//
|
|
89
|
+
// The overlay and the actions are siblings of the select button, never children: a button inside a
|
|
90
|
+
// button is invalid markup and swallows the inner click, and both layers stack above it so their
|
|
91
|
+
// own buttons get the click. They are handed the selection because their content often styles
|
|
92
|
+
// itself by it.
|
|
93
|
+
defineSlots<NexxtTemplateCardSlots>()
|
|
94
|
+
|
|
50
95
|
const featureMessages = computed<Record<NexxtTemplateCardContentFeature, string>>(() => ({
|
|
51
96
|
...DEFAULT_MESSAGES,
|
|
52
97
|
...props.messages,
|
|
53
98
|
}))
|
|
99
|
+
|
|
100
|
+
const onSelect = () => {
|
|
101
|
+
if (props.selectable && !props.disabled) emit('select')
|
|
102
|
+
}
|
|
54
103
|
</script>
|
|
55
104
|
|
|
56
105
|
<template>
|
|
57
|
-
<div
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
106
|
+
<div
|
|
107
|
+
class="group relative size-full max-w-105 rounded-xl border border-gray-200 transition-colors duration-200 ease-out"
|
|
108
|
+
:class="[
|
|
109
|
+
selectable && selected && 'border-cornflower-blue-500',
|
|
110
|
+
selectable && !selected && !disabled && 'hover:border-gray-300',
|
|
111
|
+
]"
|
|
112
|
+
>
|
|
113
|
+
<div
|
|
114
|
+
class="relative z-10 transition-opacity duration-200 ease-out"
|
|
115
|
+
:class="[
|
|
116
|
+
// Lets a click anywhere on the card reach the stretched button underneath; only the
|
|
117
|
+
// tooltip triggers claim their own pointer events back.
|
|
118
|
+
selectable && 'pointer-events-none',
|
|
119
|
+
selectable && disabled && 'opacity-50',
|
|
120
|
+
$slots.overlay &&
|
|
121
|
+
!(selectable && disabled) &&
|
|
122
|
+
'group-focus-within:opacity-40 group-hover:opacity-40 pointer-coarse:opacity-100!',
|
|
123
|
+
]"
|
|
124
|
+
>
|
|
125
|
+
<picture class="block h-38 w-full">
|
|
126
|
+
<!-- Decorative: the template name is right below it as text. -->
|
|
127
|
+
<img
|
|
128
|
+
v-if="image"
|
|
129
|
+
:src="image"
|
|
130
|
+
alt=""
|
|
131
|
+
loading="lazy"
|
|
132
|
+
class="h-38 w-full rounded-t-xl object-cover"
|
|
133
|
+
/>
|
|
134
|
+
<div v-else class="flex h-38 w-full items-center justify-center rounded-t-xl bg-gray-100">
|
|
135
|
+
<NexxtIcon name="image" class="text-4xl text-gray-400" />
|
|
136
|
+
</div>
|
|
137
|
+
</picture>
|
|
138
|
+
<div class="rounded-b-xl px-8 pt-3.5 pb-4">
|
|
139
|
+
<h3 class="truncate heading-4 text-gray-900">{{ title }}</h3>
|
|
140
|
+
<p v-if="subtitle" class="truncate body-normal text-gray-700">{{ subtitle }}</p>
|
|
141
|
+
<ul v-if="contentFeatures.length" class="mt-3 flex flex-wrap gap-2">
|
|
142
|
+
<li v-for="feature in contentFeatures" :key="feature">
|
|
143
|
+
<NexxtTooltip>
|
|
144
|
+
<!--
|
|
145
|
+
Claims its pointer events back from the content layer so the tooltip still opens on
|
|
146
|
+
hover, and forwards the click to the card so the icon is no dead zone on a
|
|
147
|
+
selectable card. `onSelect` guards on selectable and disabled itself.
|
|
148
|
+
NexxtIcon is aria-hidden and the trigger is not focusable, so the label is only
|
|
149
|
+
there for screen readers.
|
|
150
|
+
-->
|
|
151
|
+
<div
|
|
152
|
+
:class="[ICON_MAP[feature].backgroundColor, ICON_MAP[feature].textColor]"
|
|
153
|
+
class="pointer-events-auto flex size-8 items-center justify-center rounded-full"
|
|
154
|
+
@click="onSelect"
|
|
155
|
+
>
|
|
156
|
+
<NexxtIcon :name="ICON_MAP[feature].icon" />
|
|
157
|
+
<span class="sr-only">{{ featureMessages[feature] }}</span>
|
|
158
|
+
</div>
|
|
159
|
+
<template #tooltip>
|
|
160
|
+
<span class="small-normal text-gray-700">{{ featureMessages[feature] }}</span>
|
|
161
|
+
</template>
|
|
162
|
+
</NexxtTooltip>
|
|
163
|
+
</li>
|
|
164
|
+
</ul>
|
|
68
165
|
</div>
|
|
69
|
-
</picture>
|
|
70
|
-
<div class="rounded-b-xl px-8 pt-3.5 pb-4">
|
|
71
|
-
<h3 class="truncate heading-4 text-gray-900">{{ title }}</h3>
|
|
72
|
-
<p v-if="subtitle" class="truncate body-normal text-gray-700">{{ subtitle }}</p>
|
|
73
|
-
<ul v-if="contentFeatures.length" class="mt-3 flex flex-wrap gap-2">
|
|
74
|
-
<li v-for="feature in contentFeatures" :key="feature">
|
|
75
|
-
<NexxtTooltip>
|
|
76
|
-
<div
|
|
77
|
-
:class="[ICON_MAP[feature].backgroundColor, ICON_MAP[feature].textColor]"
|
|
78
|
-
class="flex size-8 items-center justify-center rounded-full"
|
|
79
|
-
>
|
|
80
|
-
<NexxtIcon :name="ICON_MAP[feature].icon" />
|
|
81
|
-
</div>
|
|
82
|
-
<template #tooltip>
|
|
83
|
-
<span class="small-normal text-gray-700">{{ featureMessages[feature] }}</span>
|
|
84
|
-
</template>
|
|
85
|
-
</NexxtTooltip>
|
|
86
|
-
</li>
|
|
87
|
-
</ul>
|
|
88
166
|
</div>
|
|
167
|
+
|
|
168
|
+
<!--
|
|
169
|
+
A stretched button instead of a wrapper: it carries the click, the pressed state and the
|
|
170
|
+
focus ring, while the card content above it stays real document structure. It holds the
|
|
171
|
+
lowest stacking level of the layers, so a click on an actions or overlay button lands there
|
|
172
|
+
and not here. The focus ring follows Button.vue (`outline-3` plus offset), so a selected card
|
|
173
|
+
with keyboard focus shows two distinguishable indicators: blue border and ring.
|
|
174
|
+
-->
|
|
175
|
+
<button
|
|
176
|
+
v-if="selectable"
|
|
177
|
+
type="button"
|
|
178
|
+
:aria-label="title"
|
|
179
|
+
:aria-pressed="selected"
|
|
180
|
+
:disabled="disabled || undefined"
|
|
181
|
+
class="absolute inset-0 cursor-pointer rounded-xl focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-cornflower-blue-500 disabled:cursor-not-allowed"
|
|
182
|
+
@click="onSelect"
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
<div v-if="$slots.actions" class="absolute top-4 right-4 z-20">
|
|
186
|
+
<slot name="actions" :selected="selected" />
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<!--
|
|
190
|
+
Dropped while selectable and disabled: an invisible layer would still hold tabbable buttons,
|
|
191
|
+
and a card that cannot be chosen reveals nothing. A read-only card (no `selectable`) keeps
|
|
192
|
+
its overlay, disabled or not.
|
|
193
|
+
`pointer-events-none` on the layer with `*:pointer-events-auto` on its children lets a click
|
|
194
|
+
on the veil fall through to the select button while the overlay's own buttons keep theirs.
|
|
195
|
+
On a coarse pointer there is no hover to reveal it, so it is always there -- but then without
|
|
196
|
+
veil or blur and shrunk to the image band at the top, because a permanent veil over the title
|
|
197
|
+
and the subtitle drops their contrast below 4.5:1 and a full-height layer would swallow the
|
|
198
|
+
taps on the feature icons.
|
|
199
|
+
jsdom resolves no Tailwind and the storybook project cannot hit-test, so the layering itself
|
|
200
|
+
is asserted by neither suite; only the DOM order is.
|
|
201
|
+
-->
|
|
202
|
+
<div
|
|
203
|
+
v-if="$slots.overlay && !(selectable && disabled)"
|
|
204
|
+
class="pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-xl bg-white/60 opacity-0 backdrop-blur-xs transition-opacity duration-200 ease-out *:pointer-events-auto group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:inset-x-0 pointer-coarse:top-0 pointer-coarse:bottom-auto pointer-coarse:h-38 pointer-coarse:justify-end pointer-coarse:bg-transparent pointer-coarse:p-4 pointer-coarse:opacity-100 pointer-coarse:backdrop-blur-none"
|
|
205
|
+
>
|
|
206
|
+
<slot name="overlay" :selected="selected" />
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<span
|
|
210
|
+
v-if="selectable && selected && checkmark"
|
|
211
|
+
class="absolute -top-2 -right-2 z-30 flex size-6 items-center justify-center rounded-full bg-cornflower-blue-500 text-white shadow-sm"
|
|
212
|
+
>
|
|
213
|
+
<NexxtIcon name="check" type="solid" class="text-xs" />
|
|
214
|
+
</span>
|
|
89
215
|
</div>
|
|
90
216
|
</template>
|
package/src/components.json
CHANGED
|
@@ -60,5 +60,6 @@
|
|
|
60
60
|
"NexxtTimelinePhaseblock": "components/organisms/TimelinePhaseblock/TimelinePhaseblock.vue",
|
|
61
61
|
"NexxtTimelineTrack": "components/organisms/TimelineTrack/TimelineTrack.vue",
|
|
62
62
|
"NexxtToaster": "components/organisms/Toaster/Toaster.vue",
|
|
63
|
-
"NexxtVideoPlayer": "components/organisms/VideoPlayer/VideoPlayer.vue"
|
|
63
|
+
"NexxtVideoPlayer": "components/organisms/VideoPlayer/VideoPlayer.vue",
|
|
64
|
+
"NexxtEditorSettingsBlock": "components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.vue"
|
|
64
65
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,11 @@ export type { EnergyLabelLetter } from './components/atoms/EnergyLabel/EnergyLab
|
|
|
13
13
|
export type { IconType, NexxtIconName } from './components/atoms/Icon/Icon.vue'
|
|
14
14
|
export type { CustomIconName } from './components/atoms/Icon/customIcons'
|
|
15
15
|
export type { NexxtTabItem } from './components/molecules/Tabs/Tabs.vue'
|
|
16
|
+
export type {
|
|
17
|
+
NexxtTemplateCardContentFeature,
|
|
18
|
+
NexxtTemplateCardProps,
|
|
19
|
+
NexxtTemplateCardSlots,
|
|
20
|
+
} from './components/molecules/TemplateCard/TemplateCard.vue'
|
|
16
21
|
|
|
17
22
|
export { default as NexxtAnimatedNumber } from './components/atoms/AnimatedNumber/AnimatedNumber.vue'
|
|
18
23
|
export { default as NexxtAnnouncement } from './components/molecules/Announcement/Announcement.vue'
|
|
@@ -31,6 +36,7 @@ export { default as NexxtCustomerJourneyTile } from './components/molecules/Cust
|
|
|
31
36
|
export { default as NexxtDatePicker } from './components/organisms/DatePicker/DatePicker.vue'
|
|
32
37
|
export { default as NexxtDoughnutChart } from './components/molecules/DoughnutChart/DoughnutChart.vue'
|
|
33
38
|
export { default as NexxtDropdownButton } from './components/organisms/DropdownButton/DropdownButton.vue'
|
|
39
|
+
export { default as NexxtEditorSettingsBlock } from './components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.vue'
|
|
34
40
|
export { default as NexxtEnergyLabel } from './components/atoms/EnergyLabel/EnergyLabel.vue'
|
|
35
41
|
export { default as NexxtFloatingPanel } from './components/atoms/FloatingPanel/FloatingPanel.vue'
|
|
36
42
|
export { default as NexxtHeader } from './components/organisms/Header/Header.vue'
|
package/src/nuxt.ts
CHANGED
|
@@ -92,8 +92,21 @@ const module: NuxtModule<ModuleOptions> = defineNuxtModule<ModuleOptions>({
|
|
|
92
92
|
.replace(/\/+/g, '/')
|
|
93
93
|
.replace(/^\/|\/$/g, '')}/`
|
|
94
94
|
|
|
95
|
-
nuxt.
|
|
96
|
-
nuxt
|
|
95
|
+
// @nuxt/schema 4.x no longer declares `nitro` on NuxtOptions; the typing
|
|
96
|
+
// arrives through module augmentation in @nuxt/nitro-server, which ships
|
|
97
|
+
// with `nuxt` itself. This package depends on @nuxt/kit and @nuxt/schema
|
|
98
|
+
// only — deliberately, to stay free of app coupling — so the augmentation
|
|
99
|
+
// is absent here and the property has to be described locally. At runtime
|
|
100
|
+
// in a consumer the object is always present. Intersecting rather than
|
|
101
|
+
// replacing keeps this compatible with an installation that does have the
|
|
102
|
+
// augmentation: `options` stays the same object, so the mutation below
|
|
103
|
+
// still targets `nuxt.options.nitro`.
|
|
104
|
+
const options = nuxt.options as typeof nuxt.options & {
|
|
105
|
+
nitro: { publicAssets?: { dir: string; baseURL?: string; maxAge?: number }[] }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
options.nitro.publicAssets ??= []
|
|
109
|
+
options.nitro.publicAssets.push({
|
|
97
110
|
dir: assetsDir,
|
|
98
111
|
baseURL,
|
|
99
112
|
// Content-hashed filenames, so they are safe to cache indefinitely.
|