@nexxtmove/ui 1.34.0 → 1.35.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "1.34.0",
4
+ "version": "1.35.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -14,10 +14,11 @@ export type EditorColorProperty = 'bg' | 'text'
14
14
  */
15
15
  export type EditorColorValue = `${EditorColorProperty}-[#${string}]`
16
16
 
17
- /** Overridable copy of the picker. Every key is optional. */
18
17
  export interface EditorColorMessages {
19
18
  /** Text shown while no colour is set. Defaults to `'Kies kleur...'`. */
20
19
  placeholder?: string
20
+ /** Label shown above reusable colours. */
21
+ suggestionsLabel?: string
21
22
  }
22
23
 
23
24
  export interface NexxtEditorColorProps {
@@ -44,10 +45,13 @@ export interface NexxtEditorColorProps {
44
45
  * defaults of the others. Copy is never translated inside the component.
45
46
  */
46
47
  messages?: EditorColorMessages
48
+ /** Hex colours available inside the picker panel as one-click suggestions. */
49
+ suggestions?: string[]
47
50
  }
48
51
 
49
52
  const DEFAULT_MESSAGES = {
50
53
  placeholder: 'Kies kleur...',
54
+ suggestionsLabel: 'Gebruikte kleuren',
51
55
  } satisfies Required<EditorColorMessages>
52
56
 
53
57
  /**
@@ -72,6 +76,7 @@ const {
72
76
  switchable = false,
73
77
  property = 'bg',
74
78
  messages,
79
+ suggestions = [],
75
80
  } = defineProps<NexxtEditorColorProps>()
76
81
 
77
82
  /**
@@ -150,6 +155,8 @@ watch(
150
155
  :formats="['hex']"
151
156
  :show-alpha="false"
152
157
  :placeholder="copy.placeholder"
158
+ :suggestions="{ hex: suggestions }"
159
+ :suggestions-label="copy.suggestionsLabel"
153
160
  />
154
161
  </NexxtEditorSettingsBlock>
155
162
  </template>
@@ -19,6 +19,7 @@ import NexxtFloatingPanel from '../../atoms/FloatingPanel/FloatingPanel.vue'
19
19
  import NexxtIcon from '../../atoms/Icon/Icon.vue'
20
20
 
21
21
  export type ColorFormat = 'hex' | 'hsl' | 'rgb'
22
+ export type ColorSuggestions = Partial<Record<ColorFormat, string[]>>
22
23
 
23
24
  export interface NexxtColorPickerProps {
24
25
  label?: string
@@ -30,6 +31,10 @@ export interface NexxtColorPickerProps {
30
31
  formats?: ColorFormat[]
31
32
  fullWidth?: boolean
32
33
  matchWidth?: boolean
34
+ /** Existing colours grouped by format; only suggestions for the active format are shown. */
35
+ suggestions?: ColorSuggestions
36
+ /** Accessible and visible label for the suggestions. */
37
+ suggestionsLabel?: string
33
38
  }
34
39
 
35
40
  defineOptions({ name: 'NexxtColorPicker' })
@@ -42,22 +47,62 @@ const props = withDefaults(defineProps<NexxtColorPickerProps>(), {
42
47
  formats: () => ['hex'],
43
48
  fullWidth: false,
44
49
  matchWidth: false,
50
+ suggestions: () => ({}),
51
+ suggestionsLabel: 'Gebruikte kleuren',
45
52
  })
46
53
 
47
54
  const model = defineModel<string>({ default: '' })
48
55
 
49
- const colorObj = ref<Color>(normalizeColor(model.value || '#3b82f6'))
50
- const hexInputError = ref('')
56
+ const withoutAlpha = (color: Color) =>
57
+ color.alpha < 1 ? setChannelValue(color, 'alpha', 100) : color
58
+
59
+ const normalizeModelColor = (value: string) => {
60
+ const color = normalizeColor(value)
61
+
62
+ return props.showAlpha ? color : withoutAlpha(color)
63
+ }
64
+
65
+ const normalizeInputColor = (value: string) => {
66
+ const color = normalizeColor(value)
67
+
68
+ if (!props.showAlpha && color.alpha < 1) {
69
+ throw new Error('Transparantie is niet beschikbaar')
70
+ }
71
+
72
+ return color
73
+ }
74
+
51
75
  const activeFormatIndex = ref(0)
76
+ const activeFormat = computed<ColorFormat>(() => props.formats[activeFormatIndex.value] ?? 'hex')
77
+ const fmt = (color: Color, format: ColorFormat) => {
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ return colorToString(color, format as any)
80
+ }
81
+
82
+ const colorObj = ref<Color>(normalizeModelColor(model.value || '#3b82f6'))
83
+ const hexInputError = ref('')
52
84
  const channelInputErrors = ref<Partial<Record<ColorChannel, string>>>({})
53
85
 
54
86
  const hexColor = computed(() => colorToString(colorObj.value, 'hex'))
55
- const activeFormat = computed<ColorFormat>(() => props.formats[activeFormatIndex.value] ?? 'hex')
56
87
  const displayValue = computed(() => (model.value ? fmt(colorObj.value, activeFormat.value) : ''))
88
+ const colorSuggestions = computed(() => {
89
+ const values = new Set<string>()
57
90
 
58
- const fmt = (color: Color, format: ColorFormat) => {
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- return colorToString(color, format as any)
91
+ props.suggestions[activeFormat.value]?.forEach((suggestion) => {
92
+ try {
93
+ const color = normalizeInputColor(suggestion)
94
+ values.add(fmt(color, activeFormat.value))
95
+ } catch {
96
+ // Consumers can derive suggestions from stored classes; ignore malformed or unsupported values safely.
97
+ }
98
+ })
99
+
100
+ return [...values]
101
+ })
102
+
103
+ const selectSuggestion = (suggestion: string) => {
104
+ colorObj.value = normalizeInputColor(suggestion)
105
+ model.value = fmt(colorObj.value, activeFormat.value)
61
106
  }
62
107
 
63
108
  const cycleFormat = (direction: 1 | -1) => {
@@ -67,14 +112,15 @@ const cycleFormat = (direction: 1 | -1) => {
67
112
  }
68
113
 
69
114
  const handleColorUpdate = (newColor: Color) => {
70
- colorObj.value = newColor
71
- model.value = fmt(newColor, activeFormat.value)
115
+ const updated = props.showAlpha ? newColor : withoutAlpha(newColor)
116
+ colorObj.value = updated
117
+ model.value = fmt(updated, activeFormat.value)
72
118
  }
73
119
 
74
120
  const handleHexInput = (event: Event) => {
75
121
  const val = (event.target as HTMLInputElement).value
76
122
  try {
77
- colorObj.value = normalizeColor(val)
123
+ colorObj.value = normalizeInputColor(val)
78
124
  model.value = fmt(colorObj.value, activeFormat.value)
79
125
  hexInputError.value = ''
80
126
  } catch (e) {
@@ -103,12 +149,23 @@ watch(
103
149
  (val) => {
104
150
  if (!val) return
105
151
  try {
106
- colorObj.value = normalizeColor(val)
152
+ colorObj.value = normalizeModelColor(val)
107
153
  } catch {
108
154
  // Ignore invalid color strings
109
155
  }
110
156
  },
111
157
  )
158
+
159
+ watch(
160
+ () => props.showAlpha,
161
+ (showAlpha) => {
162
+ if (!showAlpha && colorObj.value.alpha < 1) {
163
+ const updated = withoutAlpha(colorObj.value)
164
+ colorObj.value = updated
165
+ model.value = fmt(updated, activeFormat.value)
166
+ }
167
+ },
168
+ )
112
169
  </script>
113
170
 
114
171
  <template>
@@ -386,6 +443,25 @@ watch(
386
443
  </ColorFieldRoot>
387
444
  </div>
388
445
  </div>
446
+ <div v-if="colorSuggestions.length" class="flex flex-col gap-1">
447
+ <span class="extra-small-normal text-gray-500">{{ suggestionsLabel }}</span>
448
+ <div class="flex flex-wrap gap-1.5">
449
+ <button
450
+ v-for="suggestion in colorSuggestions"
451
+ :key="suggestion"
452
+ type="button"
453
+ class="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"
454
+ :aria-label="`Kies kleur ${suggestion}`"
455
+ @click="selectSuggestion(suggestion)"
456
+ >
457
+ <ColorSwatch
458
+ :color="suggestion"
459
+ class="size-6 rounded-sm shadow-[inset_0_0_0_1px_rgba(0,0,0,0.1)]"
460
+ :style="{ backgroundColor: 'var(--reka-color-swatch-color)' }"
461
+ />
462
+ </button>
463
+ </div>
464
+ </div>
389
465
  </div>
390
466
  </template>
391
467
  </NexxtFloatingPanel>