@nexxtmove/ui 1.36.0 → 1.37.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/nuxt.js CHANGED
@@ -83,6 +83,8 @@ var s = {
83
83
  NexxtEditorGap: "components/editor/molecules/EditorGap/EditorGap.vue",
84
84
  NexxtEditorHeadingType: "components/editor/molecules/EditorHeadingType/EditorHeadingType.vue",
85
85
  NexxtEditorJustifyContent: "components/editor/molecules/EditorJustifyContent/EditorJustifyContent.vue",
86
+ NexxtEditorLetterSpacing: "components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.vue",
87
+ NexxtEditorLineHeight: "components/editor/molecules/EditorLineHeight/EditorLineHeight.vue",
86
88
  NexxtEditorMaxWidth: "components/editor/molecules/EditorMaxWidth/EditorMaxWidth.vue",
87
89
  NexxtEditorPadding: "components/editor/molecules/EditorPadding/EditorPadding.vue",
88
90
  NexxtEditorRadius: "components/editor/molecules/EditorRadius/EditorRadius.vue",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "1.36.0",
4
+ "version": "1.37.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -0,0 +1,163 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The steps the slider can land on, as the class they are stored as and the
4
+ * pixels that class is. Not a scale Tailwind has names for -- `tracking-wide`
5
+ * is an em and moves with the font size, and the editor shows every size in
6
+ * px -- so every step is an arbitrary-value class.
7
+ *
8
+ * The middle step is no class at all: zero is what the text already does, and
9
+ * storing nothing for it keeps the "no class means default" every other
10
+ * setting has. The negative side is there on purpose: tightening a large
11
+ * heading is a real need.
12
+ *
13
+ * The consumer stores these classes and lists them in an `@source inline` rule,
14
+ * so adding or renaming a token is a breaking change even though the component
15
+ * API stays the same.
16
+ */
17
+ export const EDITOR_LETTER_SPACING_STEPS = [
18
+ { token: 'tracking-[-1px]', px: -1 },
19
+ { token: 'tracking-[-0.5px]', px: -0.5 },
20
+ { token: 'tracking-[-0.25px]', px: -0.25 },
21
+ { token: null, px: 0 },
22
+ { token: 'tracking-[0.25px]', px: 0.25 },
23
+ { token: 'tracking-[0.5px]', px: 0.5 },
24
+ { token: 'tracking-[1px]', px: 1 },
25
+ { token: 'tracking-[1.5px]', px: 1.5 },
26
+ { token: 'tracking-[2px]', px: 2 },
27
+ ] as const
28
+
29
+ /**
30
+ * The letter spacing as the editor needs it: the Tailwind class itself, so the
31
+ * consumer can put the model value straight on the element it styles. The
32
+ * default step is `null` on the model, not a member of this type.
33
+ */
34
+ export type EditorLetterSpacingValue = NonNullable<
35
+ (typeof EDITOR_LETTER_SPACING_STEPS)[number]['token']
36
+ >
37
+
38
+ /** Overridable copy of the value readout. Every key is optional. */
39
+ export interface EditorLetterSpacingMessages {
40
+ /** Unit shown behind the value. Defaults to `'px'`. */
41
+ unit?: string
42
+ /** Decimal separator of the readout. Defaults to the Dutch `','`. */
43
+ decimalSeparator?: string
44
+ }
45
+
46
+ export interface NexxtEditorLetterSpacingProps {
47
+ /**
48
+ * Header text of the settings block. Defaults to the Dutch
49
+ * `'Letterafstand'`; the library has no i18n layer, so a consumer in another
50
+ * language passes its own copy in.
51
+ */
52
+ label?: string
53
+ /**
54
+ * Copy of the value readout. Merged per key, so overriding one key keeps the
55
+ * defaults of the others. Copy is never translated inside the component.
56
+ */
57
+ messages?: EditorLetterSpacingMessages
58
+ }
59
+
60
+ const DEFAULT_MESSAGES = {
61
+ unit: 'px',
62
+ decimalSeparator: ',',
63
+ } satisfies Required<EditorLetterSpacingMessages>
64
+
65
+ /** Index of the no-class step, the middle of the list. */
66
+ const DEFAULT_INDEX = EDITOR_LETTER_SPACING_STEPS.findIndex(({ token }) => token === null)
67
+ </script>
68
+
69
+ <script lang="ts" setup>
70
+ import { computed } from 'vue'
71
+ import NexxtEditorSettingsBlock from '../../atoms/EditorSettingsBlock/EditorSettingsBlock.vue'
72
+ import NexxtSlider from '../../../atoms/Slider/Slider.vue'
73
+
74
+ defineOptions({ name: 'NexxtEditorLetterSpacing' })
75
+
76
+ const { label = 'Letterafstand', messages } = defineProps<NexxtEditorLetterSpacingProps>()
77
+
78
+ const emit = defineEmits<{
79
+ /**
80
+ * The slider was let go: the end of one drag, which a consumer can use to
81
+ * close an undo step. Arrow keys emit no pointerup.
82
+ */
83
+ pointerup: [event: PointerEvent]
84
+ }>()
85
+
86
+ /**
87
+ * The selected letter spacing, as the Tailwind class to apply. `null` -- also
88
+ * what an unset model is -- is the default step: no class at all, zero.
89
+ */
90
+ const model = defineModel<EditorLetterSpacingValue | null>({ default: null })
91
+
92
+ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
93
+
94
+ const maxIndex = EDITOR_LETTER_SPACING_STEPS.length - 1
95
+
96
+ /**
97
+ * Position of the current value on the step list. A value that is not on the
98
+ * list -- a stored `tracking-wide`, say -- falls back to the default step
99
+ * rather than the first one: the first step here is the tightest spacing, and
100
+ * an unknown value is no reason to show text as squeezed. The first change
101
+ * writes a valid class back.
102
+ */
103
+ const stepIndex = computed(() => {
104
+ const index = EDITOR_LETTER_SPACING_STEPS.findIndex(({ token }) => token === model.value)
105
+
106
+ return index === -1 ? DEFAULT_INDEX : index
107
+ })
108
+
109
+ // The slider moves over the index, not over the px value, so its model is
110
+ // translated both ways here.
111
+ const sliderValue = computed({
112
+ get: () => [stepIndex.value],
113
+ set: ([index]) => {
114
+ model.value = EDITOR_LETTER_SPACING_STEPS[index].token
115
+ },
116
+ })
117
+
118
+ /** `-0,5 px`: a plain hyphen-minus for the sign, the decimal separator from the copy. */
119
+ const displayValue = computed(() => {
120
+ const { px } = EDITOR_LETTER_SPACING_STEPS[stepIndex.value]
121
+
122
+ return `${String(px).replace('.', copy.value.decimalSeparator)} ${copy.value.unit}`
123
+ })
124
+ </script>
125
+
126
+ <template>
127
+ <NexxtEditorSettingsBlock :label="label">
128
+ <div class="flex items-center gap-4">
129
+ <!--
130
+ Slider's own `showValue` prints its model, which here is the step index
131
+ rather than the spacing, so the readout is rendered next to it instead.
132
+ The thumb has the same problem in `aria-valuenow`, which
133
+ `aria-valuetext` overrides with the string the readout shows. The block
134
+ label is a <p>, not a <label>, so the name goes on the thumb explicitly.
135
+ Slider binds `$attrs` on its root, so the pointerup listener lands there
136
+ and catches the release of the captured pointer.
137
+ -->
138
+ <NexxtSlider
139
+ v-model="sliderValue"
140
+ :min="0"
141
+ :max="maxIndex"
142
+ :step="1"
143
+ :aria-label="label"
144
+ :aria-valuetext="displayValue"
145
+ @pointerup="emit('pointerup', $event)"
146
+ />
147
+
148
+ <!--
149
+ `min-w-16` rather than the `min-w-12` of the other editor sliders:
150
+ `-0,25 px` does not fit in 12. That plus `shrink-0` fixes the width of
151
+ the box, so the slider keeps its size while dragging; `tabular-nums`
152
+ only stops the digits from jittering inside it.
153
+ gray-700, not the gray-500 Slider uses for its own readout: axe puts
154
+ gray-500 on white at 1.84:1, well under the 4.5:1 it needs at this size.
155
+ -->
156
+ <span
157
+ class="min-w-16 shrink-0 text-right small-normal whitespace-nowrap text-gray-700 tabular-nums"
158
+ >
159
+ {{ displayValue }}
160
+ </span>
161
+ </div>
162
+ </NexxtEditorSettingsBlock>
163
+ </template>
@@ -0,0 +1,161 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The steps the slider can land on, as the class they are stored as and the
4
+ * pixels that class is. The `leading-*` classes are Tailwind's spacing scale
5
+ * (calc of `--spacing`, 4px a step), so `leading-6` really is 24px -- and a
6
+ * line height set here does not scale with the font size; that is the deal the
7
+ * fixed steps make, and nothing here tries to compensate for it.
8
+ *
9
+ * The first step is no class at all: the element keeps its own line height.
10
+ * It has no px of its own, so the readout names it instead of measuring it.
11
+ *
12
+ * The consumer stores these classes and lists them in an `@source inline` rule,
13
+ * so adding or renaming a token is a breaking change even though the component
14
+ * API stays the same.
15
+ */
16
+ export const EDITOR_LINE_HEIGHT_STEPS = [
17
+ { token: null, px: null },
18
+ { token: 'leading-3', px: 12 },
19
+ { token: 'leading-4', px: 16 },
20
+ { token: 'leading-5', px: 20 },
21
+ { token: 'leading-6', px: 24 },
22
+ { token: 'leading-7', px: 28 },
23
+ { token: 'leading-8', px: 32 },
24
+ { token: 'leading-9', px: 36 },
25
+ { token: 'leading-10', px: 40 },
26
+ { token: 'leading-11', px: 44 },
27
+ { token: 'leading-12', px: 48 },
28
+ { token: 'leading-14', px: 56 },
29
+ { token: 'leading-16', px: 64 },
30
+ ] as const
31
+
32
+ /**
33
+ * The line height as the editor needs it: the Tailwind class itself, so the
34
+ * consumer can put the model value straight on the element it styles. The
35
+ * default step is `null` on the model, not a member of this type.
36
+ */
37
+ export type EditorLineHeightValue = NonNullable<(typeof EDITOR_LINE_HEIGHT_STEPS)[number]['token']>
38
+
39
+ /** Overridable copy of the value readout. Every key is optional. */
40
+ export interface EditorLineHeightMessages {
41
+ /** Unit shown behind the value. Defaults to `'px'`. */
42
+ unit?: string
43
+ /**
44
+ * Readout of the first step, no class, which has no px size. Defaults to
45
+ * the Dutch `'Standaard'`.
46
+ */
47
+ default?: string
48
+ }
49
+
50
+ export interface NexxtEditorLineHeightProps {
51
+ /**
52
+ * Header text of the settings block. Defaults to the Dutch
53
+ * `'Regelafstand'`; the library has no i18n layer, so a consumer in another
54
+ * language passes its own copy in.
55
+ */
56
+ label?: string
57
+ /**
58
+ * Copy of the value readout. Merged per key, so overriding one key keeps the
59
+ * defaults of the others. Copy is never translated inside the component.
60
+ */
61
+ messages?: EditorLineHeightMessages
62
+ }
63
+
64
+ const DEFAULT_MESSAGES = {
65
+ unit: 'px',
66
+ default: 'Standaard',
67
+ } satisfies Required<EditorLineHeightMessages>
68
+ </script>
69
+
70
+ <script lang="ts" setup>
71
+ import { computed } from 'vue'
72
+ import NexxtEditorSettingsBlock from '../../atoms/EditorSettingsBlock/EditorSettingsBlock.vue'
73
+ import NexxtSlider from '../../../atoms/Slider/Slider.vue'
74
+
75
+ defineOptions({ name: 'NexxtEditorLineHeight' })
76
+
77
+ const { label = 'Regelafstand', messages } = defineProps<NexxtEditorLineHeightProps>()
78
+
79
+ const emit = defineEmits<{
80
+ /**
81
+ * The slider was let go: the end of one drag, which a consumer can use to
82
+ * close an undo step. Arrow keys emit no pointerup.
83
+ */
84
+ pointerup: [event: PointerEvent]
85
+ }>()
86
+
87
+ /**
88
+ * The selected line height, as the Tailwind class to apply. `null` -- also
89
+ * what an unset model is -- is the default step: no class at all.
90
+ */
91
+ const model = defineModel<EditorLineHeightValue | null>({ default: null })
92
+
93
+ const copy = computed(() => ({ ...DEFAULT_MESSAGES, ...messages }))
94
+
95
+ const maxIndex = EDITOR_LINE_HEIGHT_STEPS.length - 1
96
+
97
+ /**
98
+ * Position of the current value on the step list. A value that is not on the
99
+ * list -- a stored `leading-13`, say -- falls back to the first step, the
100
+ * default, instead of leaving the thumb off the track; the first change writes
101
+ * a valid class back.
102
+ */
103
+ const stepIndex = computed(() =>
104
+ Math.max(
105
+ 0,
106
+ EDITOR_LINE_HEIGHT_STEPS.findIndex(({ token }) => token === model.value),
107
+ ),
108
+ )
109
+
110
+ // The slider moves over the index, not over the px value, so its model is
111
+ // translated both ways here.
112
+ const sliderValue = computed({
113
+ get: () => [stepIndex.value],
114
+ set: ([index]) => {
115
+ model.value = EDITOR_LINE_HEIGHT_STEPS[index].token
116
+ },
117
+ })
118
+
119
+ const displayValue = computed(() => {
120
+ const { px } = EDITOR_LINE_HEIGHT_STEPS[stepIndex.value]
121
+
122
+ return px === null ? copy.value.default : `${px} ${copy.value.unit}`
123
+ })
124
+ </script>
125
+
126
+ <template>
127
+ <NexxtEditorSettingsBlock :label="label">
128
+ <div class="flex items-center gap-4">
129
+ <!--
130
+ Slider's own `showValue` prints its model, which here is the step index
131
+ rather than the line height, so the readout is rendered next to it
132
+ instead. The thumb has the same problem in `aria-valuenow`, which
133
+ `aria-valuetext` overrides with the string the readout shows. The block
134
+ label is a <p>, not a <label>, so the name goes on the thumb explicitly.
135
+ Slider binds `$attrs` on its root, so the pointerup listener lands there
136
+ and catches the release of the captured pointer.
137
+ -->
138
+ <NexxtSlider
139
+ v-model="sliderValue"
140
+ :min="0"
141
+ :max="maxIndex"
142
+ :step="1"
143
+ :aria-label="label"
144
+ :aria-valuetext="displayValue"
145
+ @pointerup="emit('pointerup', $event)"
146
+ />
147
+
148
+ <!--
149
+ `min-w-16` so `Standaard` fits and the slider keeps its size moving off
150
+ the default step; `tabular-nums` stops the digits from jittering.
151
+ gray-700, not the gray-500 Slider uses for its own readout: axe puts
152
+ gray-500 on white at 1.84:1, well under the 4.5:1 it needs at this size.
153
+ -->
154
+ <span
155
+ class="min-w-16 shrink-0 text-right small-normal whitespace-nowrap text-gray-700 tabular-nums"
156
+ >
157
+ {{ displayValue }}
158
+ </span>
159
+ </div>
160
+ </NexxtEditorSettingsBlock>
161
+ </template>
@@ -80,6 +80,8 @@
80
80
  "NexxtEditorGap": "components/editor/molecules/EditorGap/EditorGap.vue",
81
81
  "NexxtEditorHeadingType": "components/editor/molecules/EditorHeadingType/EditorHeadingType.vue",
82
82
  "NexxtEditorJustifyContent": "components/editor/molecules/EditorJustifyContent/EditorJustifyContent.vue",
83
+ "NexxtEditorLetterSpacing": "components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.vue",
84
+ "NexxtEditorLineHeight": "components/editor/molecules/EditorLineHeight/EditorLineHeight.vue",
83
85
  "NexxtEditorMaxWidth": "components/editor/molecules/EditorMaxWidth/EditorMaxWidth.vue",
84
86
  "NexxtEditorPadding": "components/editor/molecules/EditorPadding/EditorPadding.vue",
85
87
  "NexxtEditorRadius": "components/editor/molecules/EditorRadius/EditorRadius.vue",
package/src/env.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
- declare module '*.svg' {
3
+ // vite-svg-loader compiles a plain SVG import to a Vue component, but
4
+ // vite/client above declares `*.svg` as a URL string, and of two identical
5
+ // wildcard patterns the one loaded first wins. TypeScript picks the pattern
6
+ // with the longest prefix, so the library's own `@ui-assets` SVGs get their
7
+ // component type here regardless of load order.
8
+ declare module '@ui-assets/*.svg' {
4
9
  import type { DefineComponent } from 'vue'
5
10
  const component: DefineComponent<object, object, unknown>
6
11
  export default component
package/src/index.ts CHANGED
@@ -83,6 +83,16 @@ export type {
83
83
  EditorMaxWidthMessages,
84
84
  EditorMaxWidthValue,
85
85
  } from './components/editor/molecules/EditorMaxWidth/EditorMaxWidth.vue'
86
+ export { EDITOR_LINE_HEIGHT_STEPS } from './components/editor/molecules/EditorLineHeight/EditorLineHeight.vue'
87
+ export type {
88
+ EditorLineHeightMessages,
89
+ EditorLineHeightValue,
90
+ } from './components/editor/molecules/EditorLineHeight/EditorLineHeight.vue'
91
+ export { EDITOR_LETTER_SPACING_STEPS } from './components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.vue'
92
+ export type {
93
+ EditorLetterSpacingMessages,
94
+ EditorLetterSpacingValue,
95
+ } from './components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.vue'
86
96
  export type { EditorPaddingMessages } from './components/editor/molecules/EditorPadding/EditorPadding.vue'
87
97
  export type {
88
98
  NexxtTemplateCardContentFeature,
@@ -115,6 +125,8 @@ export { default as NexxtEditorFlexDirection } from './components/editor/molecul
115
125
  export { default as NexxtEditorGap } from './components/editor/molecules/EditorGap/EditorGap.vue'
116
126
  export { default as NexxtEditorHeadingType } from './components/editor/molecules/EditorHeadingType/EditorHeadingType.vue'
117
127
  export { default as NexxtEditorJustifyContent } from './components/editor/molecules/EditorJustifyContent/EditorJustifyContent.vue'
128
+ export { default as NexxtEditorLetterSpacing } from './components/editor/molecules/EditorLetterSpacing/EditorLetterSpacing.vue'
129
+ export { default as NexxtEditorLineHeight } from './components/editor/molecules/EditorLineHeight/EditorLineHeight.vue'
118
130
  export { default as NexxtEditorMaxWidth } from './components/editor/molecules/EditorMaxWidth/EditorMaxWidth.vue'
119
131
  export { default as NexxtEditorPadding } from './components/editor/molecules/EditorPadding/EditorPadding.vue'
120
132
  export { default as NexxtEditorRadius } from './components/editor/molecules/EditorRadius/EditorRadius.vue'