@nexxtmove/ui 2.1.0 → 2.3.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 +6 -0
- package/dist/components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.vue.d.ts +2 -2
- package/dist/components/editor/molecules/EditorBlur/EditorBlur.vue.d.ts +106 -0
- package/dist/components/editor/molecules/EditorColor/EditorColor.vue.d.ts +49 -20
- package/dist/components/editor/molecules/EditorColor/_ColorField.vue.d.ts +66 -0
- package/dist/components/editor/molecules/EditorColor/colorClass.d.ts +46 -0
- package/dist/components/editor/molecules/EditorColor/gradientClass.d.ts +80 -0
- package/dist/components/editor/molecules/EditorGap/EditorGap.vue.d.ts +2 -2
- package/dist/components/editor/molecules/EditorMaxWidth/EditorMaxWidth.vue.d.ts +2 -2
- package/dist/components/editor/molecules/EditorShadow/EditorShadow.vue.d.ts +2 -2
- package/dist/components/molecules/ToggleGroup/ToggleGroup.vue.d.ts +7 -0
- package/dist/components/organisms/ColorPicker/ColorPicker.vue.d.ts +61 -2
- package/dist/components/organisms/ColorPicker/_GradientField.vue.d.ts +23 -0
- package/dist/components/organisms/ColorPicker/_GradientPreview.vue.d.ts +11 -0
- package/dist/components/organisms/ColorPicker/_GradientStopRow.vue.d.ts +21 -0
- package/dist/components/organisms/ColorPicker/_GradientStops.vue.d.ts +19 -0
- package/dist/components/organisms/ColorPicker/gradient.d.ts +189 -0
- package/dist/components/organisms/ColorPicker/gradientGeometry.d.ts +101 -0
- package/dist/components/organisms/ColorPicker/useGradientEditor.d.ts +89 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +3591 -2182
- package/dist/nuxt.js +1 -0
- package/package.json +1 -1
- package/src/components/editor/atoms/EditorSettingsBlock/EditorSettingsBlock.vue +15 -2
- package/src/components/editor/molecules/EditorBlur/EditorBlur.vue +160 -0
- package/src/components/editor/molecules/EditorColor/EditorColor.vue +90 -171
- package/src/components/editor/molecules/EditorColor/_ColorField.vue +445 -0
- package/src/components/editor/molecules/EditorColor/colorClass.ts +61 -0
- package/src/components/editor/molecules/EditorColor/gradientClass.ts +225 -0
- package/src/components/molecules/ToggleGroup/ToggleGroup.vue +18 -3
- package/src/components/organisms/ColorPicker/ColorPicker.vue +686 -257
- package/src/components/organisms/ColorPicker/_GradientField.vue +93 -0
- package/src/components/organisms/ColorPicker/_GradientPreview.vue +401 -0
- package/src/components/organisms/ColorPicker/_GradientStopRow.vue +181 -0
- package/src/components/organisms/ColorPicker/_GradientStops.vue +218 -0
- package/src/components/organisms/ColorPicker/gradient.ts +303 -0
- package/src/components/organisms/ColorPicker/gradientGeometry.ts +216 -0
- package/src/components/organisms/ColorPicker/useGradientEditor.ts +358 -0
- package/src/components.json +1 -0
- package/src/index.ts +32 -0
package/dist/nuxt.js
CHANGED
|
@@ -84,6 +84,7 @@ var s = {
|
|
|
84
84
|
NexxtEditorAlignmentGrid: "components/editor/molecules/EditorAlignmentGrid/EditorAlignmentGrid.vue",
|
|
85
85
|
NexxtEditorAnchor: "components/editor/molecules/EditorAnchor/EditorAnchor.vue",
|
|
86
86
|
NexxtEditorBackgroundSize: "components/editor/molecules/EditorBackgroundSize/EditorBackgroundSize.vue",
|
|
87
|
+
NexxtEditorBlur: "components/editor/molecules/EditorBlur/EditorBlur.vue",
|
|
87
88
|
NexxtEditorBorder: "components/editor/molecules/EditorBorder/EditorBorder.vue",
|
|
88
89
|
NexxtEditorColor: "components/editor/molecules/EditorColor/EditorColor.vue",
|
|
89
90
|
NexxtEditorDashPattern: "components/editor/molecules/EditorDashPattern/EditorDashPattern.vue",
|
package/package.json
CHANGED
|
@@ -11,6 +11,12 @@ interface NexxtEditorSettingsBlockProps {
|
|
|
11
11
|
* When `false` the content is always visible and the model is ignored.
|
|
12
12
|
*/
|
|
13
13
|
switchable?: boolean
|
|
14
|
+
/**
|
|
15
|
+
* Id(s) of an element that describes the setting, set as `aria-describedby`
|
|
16
|
+
* on the switch. Only has an effect when `switchable` is `true`; without it
|
|
17
|
+
* no attribute is rendered.
|
|
18
|
+
*/
|
|
19
|
+
describedby?: string
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -21,7 +27,7 @@ interface NexxtEditorSettingsBlockProps {
|
|
|
21
27
|
*/
|
|
22
28
|
defineOptions({ name: 'NexxtEditorSettingsBlock' })
|
|
23
29
|
|
|
24
|
-
const { label, switchable = false } = defineProps<NexxtEditorSettingsBlockProps>()
|
|
30
|
+
const { label, switchable = false, describedby } = defineProps<NexxtEditorSettingsBlockProps>()
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
33
|
* Open state of the block, driven by the switch. Only has an effect when
|
|
@@ -80,7 +86,14 @@ const labelTag = computed(() => (promoted.value && group ? `h${group.value.headi
|
|
|
80
86
|
back to the `gap-2` that Switch.vue sets for itself.
|
|
81
87
|
-->
|
|
82
88
|
<div class="flex items-center [&>label]:flex [&>label]:w-full [&>label]:gap-3">
|
|
83
|
-
<Switch
|
|
89
|
+
<Switch
|
|
90
|
+
v-if="switchable"
|
|
91
|
+
v-model="model"
|
|
92
|
+
size="sm"
|
|
93
|
+
color="blue"
|
|
94
|
+
label-position="before"
|
|
95
|
+
:aria-describedby="describedby"
|
|
96
|
+
>
|
|
84
97
|
<span :class="['grow text-left', labelTypography, labelColor]">{{ label }}</span>
|
|
85
98
|
</Switch>
|
|
86
99
|
<component :is="labelTag" v-else :class="[labelTypography, labelColor]">
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The blur steps the editor offers, in the order they are shown. `token` is the
|
|
4
|
+
* class without its fixed `backdrop-blur-` prefix, so the class is
|
|
5
|
+
* `backdrop-blur-` plus the token -- the same shape as `EDITOR_SHADOW_OPTIONS`.
|
|
6
|
+
* "No blur" is not a step: it is the switch being off. The labels are not in
|
|
7
|
+
* here; they arrive through `messages`, keyed by token.
|
|
8
|
+
*/
|
|
9
|
+
export const EDITOR_BLUR_OPTIONS = [
|
|
10
|
+
{ token: 'xs' },
|
|
11
|
+
{ token: 'sm' },
|
|
12
|
+
{ token: 'md' },
|
|
13
|
+
{ token: 'lg' },
|
|
14
|
+
{ token: 'xl' },
|
|
15
|
+
] as const
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The blur as the editor needs it: the Tailwind class itself, so the consumer
|
|
19
|
+
* can put the model value straight on the group it styles.
|
|
20
|
+
*/
|
|
21
|
+
export type EditorBlurValue = `backdrop-blur-${(typeof EDITOR_BLUR_OPTIONS)[number]['token']}`
|
|
22
|
+
|
|
23
|
+
/** Overridable copy of the step control. Every key is optional. */
|
|
24
|
+
export interface EditorBlurMessages {
|
|
25
|
+
/**
|
|
26
|
+
* Accessible name of the group of steps. Visually hidden: the block label
|
|
27
|
+
* already says what the steps are. Defaults to the Dutch `'Sterkte'`.
|
|
28
|
+
*/
|
|
29
|
+
strength?: string
|
|
30
|
+
/** Name of the `backdrop-blur-xs` option. Defaults to `'XS'`. */
|
|
31
|
+
xs?: string
|
|
32
|
+
/** Name of the `backdrop-blur-sm` option. Defaults to `'S'`. */
|
|
33
|
+
sm?: string
|
|
34
|
+
/** Name of the `backdrop-blur-md` option. Defaults to `'M'`. */
|
|
35
|
+
md?: string
|
|
36
|
+
/** Name of the `backdrop-blur-lg` option. Defaults to `'L'`. */
|
|
37
|
+
lg?: string
|
|
38
|
+
/** Name of the `backdrop-blur-xl` option. Defaults to `'XL'`. */
|
|
39
|
+
xl?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NexxtEditorBlurProps {
|
|
43
|
+
/**
|
|
44
|
+
* Header text of the settings block. Defaults to the Dutch `'Vervagen'`; the
|
|
45
|
+
* library has no i18n layer, so a consumer in another language passes its own
|
|
46
|
+
* copy in.
|
|
47
|
+
*/
|
|
48
|
+
label?: string
|
|
49
|
+
/**
|
|
50
|
+
* Render a switch next to the label that collapses and expands the steps.
|
|
51
|
+
* Defaults to `true`: there is no "none" step, so the switch is the only way
|
|
52
|
+
* to turn the blur off. The switch state is the `enabled` model; switching
|
|
53
|
+
* off leaves the step alone.
|
|
54
|
+
*/
|
|
55
|
+
switchable?: boolean
|
|
56
|
+
/**
|
|
57
|
+
* Copy of the step control. Merged per key, so overriding one key keeps the
|
|
58
|
+
* defaults of the others. Copy is never translated inside the component.
|
|
59
|
+
*/
|
|
60
|
+
messages?: EditorBlurMessages
|
|
61
|
+
/**
|
|
62
|
+
* Id(s) of an element that describes the setting, such as a hint the
|
|
63
|
+
* consumer renders itself. Set as `aria-describedby` on the switch (when
|
|
64
|
+
* `switchable`) and on the group of steps. Without it no attribute is set.
|
|
65
|
+
*/
|
|
66
|
+
describedby?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const DEFAULT_MESSAGES = {
|
|
70
|
+
strength: 'Sterkte',
|
|
71
|
+
xs: 'XS',
|
|
72
|
+
sm: 'S',
|
|
73
|
+
md: 'M',
|
|
74
|
+
lg: 'L',
|
|
75
|
+
xl: 'XL',
|
|
76
|
+
} satisfies Required<EditorBlurMessages>
|
|
77
|
+
|
|
78
|
+
/** The class a control starts on when it is switched on without a valid one. */
|
|
79
|
+
const DEFAULT_VALUE: EditorBlurValue = 'backdrop-blur-md'
|
|
80
|
+
|
|
81
|
+
const toValue = (token: (typeof EDITOR_BLUR_OPTIONS)[number]['token']): EditorBlurValue =>
|
|
82
|
+
`backdrop-blur-${token}`
|
|
83
|
+
|
|
84
|
+
const isValue = (value: unknown): value is EditorBlurValue =>
|
|
85
|
+
EDITOR_BLUR_OPTIONS.some(({ token }) => toValue(token) === value)
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<script lang="ts" setup>
|
|
89
|
+
import { computed, watch } from 'vue'
|
|
90
|
+
import NexxtEditorSettingsBlock from '../../atoms/EditorSettingsBlock/EditorSettingsBlock.vue'
|
|
91
|
+
import NexxtToggleGroup from '../../../molecules/ToggleGroup/ToggleGroup.vue'
|
|
92
|
+
import type { NexxtToggleGroupOption } from '../../../molecules/ToggleGroup/ToggleGroup.vue'
|
|
93
|
+
|
|
94
|
+
defineOptions({ name: 'NexxtEditorBlur' })
|
|
95
|
+
|
|
96
|
+
const {
|
|
97
|
+
label = 'Vervagen',
|
|
98
|
+
switchable = true,
|
|
99
|
+
messages,
|
|
100
|
+
describedby,
|
|
101
|
+
} = defineProps<NexxtEditorBlurProps>()
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The selected blur, as the Tailwind class to apply (`backdrop-blur-md`).
|
|
105
|
+
* Optional: a consumer may have no class stored yet. Without a valid class the
|
|
106
|
+
* control shows `md` but writes nothing, so the consumer has to assume
|
|
107
|
+
* `backdrop-blur-md` itself. Only a switch from off to on (when `switchable`)
|
|
108
|
+
* writes `backdrop-blur-md` back; mounting never emits.
|
|
109
|
+
*/
|
|
110
|
+
const model = defineModel<EditorBlurValue>()
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Open state of the block. Only has an effect when `switchable` is `true`; then
|
|
114
|
+
* off is the way to express "no blur". With `switchable` set to `false` the
|
|
115
|
+
* steps are always active, whatever this model says.
|
|
116
|
+
*/
|
|
117
|
+
const enabled = defineModel<boolean>('enabled', { default: true })
|
|
118
|
+
|
|
119
|
+
const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
120
|
+
|
|
121
|
+
const options = computed<NexxtToggleGroupOption[]>(() =>
|
|
122
|
+
EDITOR_BLUR_OPTIONS.map(({ token }) => ({ value: toValue(token), label: copy.value[token] })),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
// ToggleGroup types its model as an open `string`; the setter narrows it, which
|
|
126
|
+
// is safe because it can only emit the option values built above. A missing or
|
|
127
|
+
// unknown class shows `md` rather than leaving every option unselected.
|
|
128
|
+
const selected = computed({
|
|
129
|
+
get: () => (isValue(model.value) ? model.value : DEFAULT_VALUE),
|
|
130
|
+
set: (value: string) => {
|
|
131
|
+
model.value = value as EditorBlurValue
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Switching on without a valid class writes `backdrop-blur-md` back, so the
|
|
136
|
+
// consumer never ends up with an enabled blur and no class to apply. Without a
|
|
137
|
+
// switch there is no switching on, so `enabled` is ignored there like everywhere
|
|
138
|
+
// else. The current model is read here rather than captured, so a parent that
|
|
139
|
+
// sets both in one tick wins.
|
|
140
|
+
watch(enabled, (on, wasOn) => {
|
|
141
|
+
if (switchable && on && !wasOn && !isValue(model.value)) model.value = DEFAULT_VALUE
|
|
142
|
+
})
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<template>
|
|
146
|
+
<NexxtEditorSettingsBlock
|
|
147
|
+
v-model="enabled"
|
|
148
|
+
:label="label"
|
|
149
|
+
:switchable="switchable"
|
|
150
|
+
:describedby="describedby"
|
|
151
|
+
>
|
|
152
|
+
<NexxtToggleGroup
|
|
153
|
+
v-model="selected"
|
|
154
|
+
:label="copy.strength"
|
|
155
|
+
label-hidden
|
|
156
|
+
:options="options"
|
|
157
|
+
:aria-describedby="describedby"
|
|
158
|
+
/>
|
|
159
|
+
</NexxtEditorSettingsBlock>
|
|
160
|
+
</template>
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { EditorColorRole, EditorColorThemeRole } from './colorClass'
|
|
3
|
+
import type { EditorColorGradientValue } from './gradientClass'
|
|
4
|
+
import type {
|
|
5
|
+
ColorPickerFillMode,
|
|
6
|
+
ColorPickerGradientMessages,
|
|
7
|
+
} from '../../../organisms/ColorPicker/ColorPicker.vue'
|
|
8
|
+
|
|
9
|
+
export type { EditorColorRole, EditorColorThemeRole } from './colorClass'
|
|
10
|
+
export type {
|
|
11
|
+
EditorColorGradientValue,
|
|
12
|
+
EditorColorLinearGradientValue,
|
|
13
|
+
EditorColorRadialGradientValue,
|
|
14
|
+
} from './gradientClass'
|
|
15
|
+
|
|
16
|
+
/** A fill the editor can offer: `'solid'` or `'gradient'`; see NexxtColorPicker. */
|
|
17
|
+
export type EditorColorFillMode = ColorPickerFillMode
|
|
18
|
+
|
|
19
|
+
/** Copy of the gradient mode; see NexxtColorPicker. */
|
|
20
|
+
export type EditorColorGradientMessages = ColorPickerGradientMessages
|
|
21
|
+
|
|
2
22
|
/**
|
|
3
23
|
* The CSS property the chosen colour is written to. It is the prefix of the
|
|
4
24
|
* Tailwind class the model carries, so it decides whether the colour becomes a
|
|
@@ -7,14 +27,6 @@
|
|
|
7
27
|
*/
|
|
8
28
|
export type EditorColorProperty = 'bg' | 'text'
|
|
9
29
|
|
|
10
|
-
const ROLES = ['main', 'accent', 'background', 'heading', 'text'] as const
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A role of the customer's theme. A colour bound to a role follows the theme,
|
|
14
|
-
* where a literal colour stays what it was picked as.
|
|
15
|
-
*/
|
|
16
|
-
export type EditorColorRole = (typeof ROLES)[number]
|
|
17
|
-
|
|
18
30
|
/** A literal colour: a Tailwind arbitrary-value class such as `'bg-[#1777EA]'`. */
|
|
19
31
|
export type EditorColorLiteralValue = `${EditorColorProperty}-[#${string}]`
|
|
20
32
|
|
|
@@ -24,22 +36,11 @@ export type EditorColorRoleValue = `${EditorColorProperty}-theme-${EditorColorRo
|
|
|
24
36
|
/**
|
|
25
37
|
* The colour as the editor needs it: the class to apply, so the consumer can
|
|
26
38
|
* put the model value straight on the element it styles. Either a literal
|
|
27
|
-
* colour or a theme role
|
|
39
|
+
* colour or a theme role, or with `modes` including `'gradient'` a gradient: a string of classes
|
|
40
|
+
* such as `'bg-linear-135 from-[#7f56d9] to-[#432e73]'`.
|
|
28
41
|
*/
|
|
29
|
-
export type EditorColorValue =
|
|
30
|
-
|
|
31
|
-
export interface EditorColorThemeRole {
|
|
32
|
-
/** The role the class is written for: `'main'` becomes `bg-theme-main`. */
|
|
33
|
-
role: EditorColorRole
|
|
34
|
-
/** Name of the role as the user reads it, in the swatch's tooltip and in the trigger. */
|
|
35
|
-
label: string
|
|
36
|
-
/**
|
|
37
|
-
* The role's current colour as a 6- or 8-digit hex. Only used to paint the
|
|
38
|
-
* swatch: the library ships no CSS for the role classes, and the hex never
|
|
39
|
-
* reaches the model.
|
|
40
|
-
*/
|
|
41
|
-
hex: string
|
|
42
|
-
}
|
|
42
|
+
export type EditorColorValue =
|
|
43
|
+
EditorColorLiteralValue | EditorColorRoleValue | EditorColorGradientValue
|
|
43
44
|
|
|
44
45
|
export interface EditorColorMessages {
|
|
45
46
|
/** Text shown while no colour is set. Defaults to `'Kies kleur...'`. */
|
|
@@ -48,6 +49,11 @@ export interface EditorColorMessages {
|
|
|
48
49
|
suggestionsLabel?: string
|
|
49
50
|
/** Label shown above the theme roles. Defaults to `'Huisstijl'`. */
|
|
50
51
|
themeLabel?: string
|
|
52
|
+
/**
|
|
53
|
+
* Label above the opacity slider, and its accessible name. Defaults to
|
|
54
|
+
* `'Dekking'`. Only shown with `showAlpha`.
|
|
55
|
+
*/
|
|
56
|
+
alphaLabel?: string
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
export interface NexxtEditorColorProps {
|
|
@@ -93,45 +99,59 @@ export interface NexxtEditorColorProps {
|
|
|
93
99
|
* alpha and the model gets the opaque 6-digit hex.
|
|
94
100
|
*/
|
|
95
101
|
showAlpha?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* The fills the picker offers. `['solid']` (the default) is the plain colour
|
|
104
|
+
* picker, unchanged. `['solid', 'gradient']`, in any order, adds an
|
|
105
|
+
* "Effen | Verloop" switch; `['gradient']` offers only the gradient, without a
|
|
106
|
+
* switch -- an empty value or a solid colour then opens on the gradient a
|
|
107
|
+
* switch would give, written only on the first change. A gradient only exists
|
|
108
|
+
* for `property` `'bg'`: with `'text'` this falls back to `['solid']`, also
|
|
109
|
+
* for `['gradient']`. A gradient is written as
|
|
110
|
+
* one string of classes in a fixed order -- type, from, from-position, via,
|
|
111
|
+
* via-position, to, to-position -- such as
|
|
112
|
+
* `'bg-linear-135 from-[#7f56d9] via-theme-main via-40% to-[#432e73cc]'` or
|
|
113
|
+
* `'bg-radial-[at_30%_70%] from-theme-accent to-[#ffffff00]'`. Hexes are in
|
|
114
|
+
* lower case without an opaque `ff`, and a position is only written when it
|
|
115
|
+
* differs from 0 %, 50 % or 100 %. In the gradient mode the opacity slider is
|
|
116
|
+
* always offered, whatever `showAlpha` says. To draw a stored gradient as an
|
|
117
|
+
* inline style, parse it with `parseGradientClass` and turn it into CSS with
|
|
118
|
+
* `gradientClassToCss`, which always interpolates in oklab, as the preview does.
|
|
119
|
+
* A radial gradient off centre renders in sRGB as a Tailwind class; draw it
|
|
120
|
+
* with `gradientClassToCss` for the oklab result the picker shows.
|
|
121
|
+
*/
|
|
122
|
+
modes?: EditorColorFillMode[]
|
|
123
|
+
/**
|
|
124
|
+
* Copy of the gradient mode. Merged per key over the Dutch defaults, so
|
|
125
|
+
* overriding one key keeps the others.
|
|
126
|
+
*/
|
|
127
|
+
gradientMessages?: EditorColorGradientMessages
|
|
128
|
+
/**
|
|
129
|
+
* Id(s) of an element that describes the setting, such as a hint the
|
|
130
|
+
* consumer renders itself. Set as `aria-describedby` on the picker's trigger,
|
|
131
|
+
* and on the switch when `switchable`. Without it no attribute is set.
|
|
132
|
+
*/
|
|
133
|
+
describedby?: string
|
|
96
134
|
}
|
|
97
135
|
|
|
98
136
|
const DEFAULT_MESSAGES = {
|
|
99
137
|
placeholder: 'Kies kleur...',
|
|
100
138
|
suggestionsLabel: 'Gebruikte kleuren',
|
|
101
139
|
themeLabel: 'Huisstijl',
|
|
140
|
+
alphaLabel: 'Dekking',
|
|
102
141
|
} satisfies Required<EditorColorMessages>
|
|
103
142
|
|
|
104
143
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* read whether or not `showAlpha` is set, so a stored `#1777EA80` survives a
|
|
109
|
-
* round trip either way.
|
|
110
|
-
*/
|
|
111
|
-
const VALUE_PATTERN = /^(bg|text)-\[(#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?)\]$/
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* A role class: one of the two prefixes, then `theme-` and one of the five
|
|
115
|
-
* roles. Either prefix takes every role. A tint such as `bg-theme-main/10` is
|
|
116
|
-
* not a value this component writes, so it is read as unparseable.
|
|
117
|
-
*/
|
|
118
|
-
const ROLE_PATTERN = new RegExp(`^(?:bg|text)-theme-(${ROLES.join('|')})$`)
|
|
119
|
-
|
|
120
|
-
/** The picker throws on a colour it cannot read, so a role's hex is checked first. */
|
|
121
|
-
const ROLE_HEX_PATTERN = /^#[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?$/
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* The picker writes `#rrggbbaa` only below full opacity, but an alpha a hair
|
|
125
|
-
* under 1 still rounds to `ff`. That pair says nothing, so it is dropped.
|
|
144
|
+
* The prefixes read as a colour. A value with the other prefix is shown and
|
|
145
|
+
* rewritten to `property`; a `border-[…]` class, like any other class this
|
|
146
|
+
* component does not own, is read as unparseable.
|
|
126
147
|
*/
|
|
127
|
-
const
|
|
148
|
+
const PROPERTIES = ['bg', 'text'] as const satisfies readonly EditorColorProperty[]
|
|
128
149
|
</script>
|
|
129
150
|
|
|
130
151
|
<script lang="ts" setup>
|
|
131
|
-
import { computed
|
|
152
|
+
import { computed } from 'vue'
|
|
132
153
|
import NexxtEditorSettingsBlock from '../../atoms/EditorSettingsBlock/EditorSettingsBlock.vue'
|
|
133
|
-
import
|
|
134
|
-
import NexxtColorPicker from '../../../organisms/ColorPicker/ColorPicker.vue'
|
|
154
|
+
import NexxtColorField from './_ColorField.vue'
|
|
135
155
|
|
|
136
156
|
defineOptions({ name: 'NexxtEditorColor' })
|
|
137
157
|
|
|
@@ -143,6 +163,9 @@ const {
|
|
|
143
163
|
suggestions = [],
|
|
144
164
|
themeRoles = [],
|
|
145
165
|
showAlpha = false,
|
|
166
|
+
modes = ['solid'],
|
|
167
|
+
gradientMessages,
|
|
168
|
+
describedby,
|
|
146
169
|
} = defineProps<NexxtEditorColorProps>()
|
|
147
170
|
|
|
148
171
|
/**
|
|
@@ -164,88 +187,15 @@ const model = defineModel<EditorColorValue>({ required: true })
|
|
|
164
187
|
const enabled = defineModel<boolean>('enabled', { default: true })
|
|
165
188
|
|
|
166
189
|
const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* The `#` is part of the model's type, so a plain `string` cannot be written
|
|
170
|
-
* into it. This narrows the picker's output and the regex capture to the shape
|
|
171
|
-
* the model promises, without a cast.
|
|
172
|
-
*/
|
|
173
|
-
const isHex = (value: string): value is `#${string}` => value.startsWith('#')
|
|
174
|
-
|
|
175
|
-
/** The hex out of the model, or an empty string when it is not a colour class. */
|
|
176
|
-
const hex = computed(() => {
|
|
177
|
-
const captured = VALUE_PATTERN.exec(model.value)?.[2]
|
|
178
|
-
return captured && isHex(captured) ? captured : ''
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
/** The role out of the model, or `undefined` when it is not a role class. */
|
|
182
|
-
const storedRole = computed(() => {
|
|
183
|
-
const captured = ROLE_PATTERN.exec(model.value)?.[1]
|
|
184
|
-
return ROLES.find((role) => role === captured)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
const offeredRoles = computed(() => themeRoles.filter((entry) => ROLE_HEX_PATTERN.test(entry.hex)))
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* The stored role as the consumer offers it. Only a role class selects a role:
|
|
191
|
-
* a literal colour that happens to equal a role's hex stays a literal.
|
|
192
|
-
*/
|
|
193
|
-
const activeRole = computed(() => offeredRoles.value.find(({ role }) => role === storedRole.value))
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Picking the role's own colour -- from the suggestions, or typed -- is how a
|
|
197
|
-
* colour is detached from the theme without changing how it looks. The picker
|
|
198
|
-
* reports nothing when it is told to pick the string it already has, so a role's
|
|
199
|
-
* hex is handed over in its 8-digit form: with `show-alpha` off the picker never
|
|
200
|
-
* reports an alpha pair, which makes every pick a change.
|
|
201
|
-
*
|
|
202
|
-
* A role has no opacity here, so an alpha pair in the role's own hex is replaced
|
|
203
|
-
* by `ff`. Handed over translucent, the picker would drop that alpha the moment
|
|
204
|
-
* the role turns `show-alpha` off, and write the opaque hex over the role.
|
|
205
|
-
*/
|
|
206
|
-
const pickerHex = (roleHex: string) => `${roleHex.slice(0, 7)}ff`
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* A role class has no opacity, so the slider is only offered for a literal.
|
|
210
|
-
* Hiding it is also what keeps the trick in `pickerHex` working: without the
|
|
211
|
-
* alpha channel the picker reads the role's hex as opaque.
|
|
212
|
-
*/
|
|
213
|
-
const alphaEnabled = computed(() => showAlpha && !activeRole.value)
|
|
214
|
-
|
|
215
|
-
// An unparseable value -- empty, a token class such as `bg-red-500`, or copy
|
|
216
|
-
// from before this component -- leaves the picker empty and its placeholder
|
|
217
|
-
// visible, rather than being reported as a problem: the first pick writes a
|
|
218
|
-
// valid class back. A role the consumer does not offer is shown the same way,
|
|
219
|
-
// because there is no label or hex to show it with; it is not written back.
|
|
220
|
-
const picked = computed({
|
|
221
|
-
get: () => (activeRole.value ? pickerHex(activeRole.value.hex) : hex.value),
|
|
222
|
-
// The picker clears its own value in no flow it offers, but an empty string
|
|
223
|
-
// would still wipe a perfectly good colour, so it is dropped rather than
|
|
224
|
-
// written through. The picker keeps rendering the model's colour.
|
|
225
|
-
set: (value: string) => {
|
|
226
|
-
const color = value.replace(OPAQUE_ALPHA_PATTERN, '$1')
|
|
227
|
-
if (isHex(color)) model.value = `${property}-[${color}]`
|
|
228
|
-
},
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
const pickRole = (role: EditorColorRole) => {
|
|
232
|
-
model.value = `${property}-theme-${role}`
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// `property` is a prop, so the parent can flip a background setting into a
|
|
236
|
-
// text-colour one while a value is already set. Rewriting the prefix here is
|
|
237
|
-
// what keeps the stored class and the prop from drifting apart.
|
|
238
|
-
watch(
|
|
239
|
-
() => property,
|
|
240
|
-
(next) => {
|
|
241
|
-
if (storedRole.value) model.value = `${next}-theme-${storedRole.value}`
|
|
242
|
-
else if (hex.value) model.value = `${next}-[${hex.value}]`
|
|
243
|
-
},
|
|
244
|
-
)
|
|
245
190
|
</script>
|
|
246
191
|
|
|
247
192
|
<template>
|
|
248
|
-
<NexxtEditorSettingsBlock
|
|
193
|
+
<NexxtEditorSettingsBlock
|
|
194
|
+
v-model="enabled"
|
|
195
|
+
:label="label"
|
|
196
|
+
:switchable="switchable"
|
|
197
|
+
:describedby="describedby"
|
|
198
|
+
>
|
|
249
199
|
<!--
|
|
250
200
|
The block label is a <p> (or the switch's own label), so the picker gets
|
|
251
201
|
no `label` of its own -- that would print the heading twice. Its trigger
|
|
@@ -258,51 +208,20 @@ watch(
|
|
|
258
208
|
floating element, so a panel in a container narrower than its content
|
|
259
209
|
would still be wider than the trigger; in the settings column it is not.
|
|
260
210
|
-->
|
|
261
|
-
<
|
|
262
|
-
v-model="
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
:
|
|
266
|
-
:show-alpha="alphaEnabled"
|
|
211
|
+
<NexxtColorField
|
|
212
|
+
v-model="model"
|
|
213
|
+
:property="property"
|
|
214
|
+
:accepts="PROPERTIES"
|
|
215
|
+
:show-alpha="showAlpha"
|
|
267
216
|
:placeholder="copy.placeholder"
|
|
268
|
-
:suggestions="
|
|
217
|
+
:suggestions="suggestions"
|
|
269
218
|
:suggestions-label="copy.suggestionsLabel"
|
|
270
|
-
:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
share a hex (heading and text), so the tooltip is what tells them
|
|
278
|
-
apart; it repeats the accessible name rather than adding copy.
|
|
279
|
-
-->
|
|
280
|
-
<div class="flex flex-wrap gap-1.5">
|
|
281
|
-
<NexxtTooltip
|
|
282
|
-
v-for="{ role, label: roleLabel, hex: roleHex } in offeredRoles"
|
|
283
|
-
:key="role"
|
|
284
|
-
disable-closing-trigger
|
|
285
|
-
>
|
|
286
|
-
<button
|
|
287
|
-
type="button"
|
|
288
|
-
:aria-label="roleLabel"
|
|
289
|
-
:aria-pressed="role === activeRole?.role"
|
|
290
|
-
class="cursor-pointer rounded-md p-0.5 ring-gray-200 transition-shadow hover:ring-2 hover:ring-cornflower-blue-400 focus-visible:ring-2 focus-visible:ring-cornflower-blue-500 focus-visible:outline-none aria-pressed:ring-2 aria-pressed:ring-cornflower-blue-500"
|
|
291
|
-
@click="pickRole(role)"
|
|
292
|
-
>
|
|
293
|
-
<span
|
|
294
|
-
aria-hidden="true"
|
|
295
|
-
class="block size-6 rounded-sm shadow-[inset_0_0_0_1px_rgba(0,0,0,0.1)]"
|
|
296
|
-
:style="{ backgroundColor: roleHex }"
|
|
297
|
-
/>
|
|
298
|
-
</button>
|
|
299
|
-
<template #tooltip>
|
|
300
|
-
<span class="small-normal text-gray-900">{{ roleLabel }}</span>
|
|
301
|
-
</template>
|
|
302
|
-
</NexxtTooltip>
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
</template>
|
|
306
|
-
</NexxtColorPicker>
|
|
219
|
+
:theme-label="copy.themeLabel"
|
|
220
|
+
:alpha-label="copy.alphaLabel"
|
|
221
|
+
:theme-roles="themeRoles"
|
|
222
|
+
:modes="modes"
|
|
223
|
+
:gradient-messages="gradientMessages"
|
|
224
|
+
:describedby="describedby"
|
|
225
|
+
/>
|
|
307
226
|
</NexxtEditorSettingsBlock>
|
|
308
227
|
</template>
|