diff --git a/packages/ui/package.json b/packages/ui/package.json
index 2ab8cc1f71..c41057aff2 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -74,6 +74,7 @@
"@xterm/xterm": "^6.0.0",
"ace-builds": "^1.43.5",
"apexcharts": "^4.0.0",
+ "colord": "^2.9.3",
"dayjs": "^1.11.10",
"dompurify": "^3.1.7",
"es-toolkit": "^1.44.0",
diff --git a/packages/ui/src/components/base/index.ts b/packages/ui/src/components/base/index.ts
index c789f4e7d8..ca06c2867f 100644
--- a/packages/ui/src/components/base/index.ts
+++ b/packages/ui/src/components/base/index.ts
@@ -71,7 +71,15 @@ export { default as I18nDebugPanel } from './I18nDebugPanel.vue'
export { default as IconSelect } from './IconSelect.vue'
export { default as InlineEditableText } from './InlineEditableText.vue'
export type { InputAppearance, InputSize } from './inputs'
-export { DateInput, DatePicker, Input, InputClearButton, InputFrame, Textarea } from './inputs'
+export {
+ ColorPicker,
+ DateInput,
+ DatePicker,
+ Input,
+ InputClearButton,
+ InputFrame,
+ Textarea,
+} from './inputs'
export { default as IntlFormatted } from './IntlFormatted.vue'
export { default as LoadingBar } from './LoadingBar.vue'
export { default as LoadingIndicator } from './LoadingIndicator.vue'
diff --git a/packages/ui/src/components/base/inputs/ColorPicker.vue b/packages/ui/src/components/base/inputs/ColorPicker.vue
new file mode 100644
index 0000000000..b7ca77e45f
--- /dev/null
+++ b/packages/ui/src/components/base/inputs/ColorPicker.vue
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/base/inputs/index.ts b/packages/ui/src/components/base/inputs/index.ts
index e396a26cd2..fce0c36a7f 100644
--- a/packages/ui/src/components/base/inputs/index.ts
+++ b/packages/ui/src/components/base/inputs/index.ts
@@ -1,3 +1,4 @@
+export { default as ColorPicker } from './ColorPicker.vue'
export { default as DateInput } from './DateInput.vue'
export { default as DatePicker } from './DatePicker.vue'
export { default as Input } from './Input.vue'
diff --git a/packages/ui/src/components/image-viewer-editor/controls.vue b/packages/ui/src/components/image-viewer-editor/controls.vue
index 96527033a5..761593306f 100644
--- a/packages/ui/src/components/image-viewer-editor/controls.vue
+++ b/packages/ui/src/components/image-viewer-editor/controls.vue
@@ -15,6 +15,7 @@ import IconButton from '#ui/components/base/buttons/IconButton.vue'
import SplitButton from '#ui/components/base/buttons/SplitButton.vue'
import type { OverflowMenuOption } from '#ui/components/base/buttons/types'
import Chips from '#ui/components/base/Chips.vue'
+import ColorPicker from '#ui/components/base/inputs/ColorPicker.vue'
import { useVIntl } from '#ui/composables/i18n'
import { commonMessages } from '#ui/utils/common-messages'
@@ -108,10 +109,6 @@ function handlePropertyInput(event: Event) {
updatePropertyValue(Number((event.target as HTMLInputElement).value))
}
-function handleColorInput(event: Event) {
- updateColor((event.target as HTMLInputElement).value)
-}
-
function formatCensorMode(value: ScreenshotCensorMode) {
return formatMessage(value === 'blur' ? messages.blur : messages.solid)
}
@@ -153,27 +150,14 @@ function formatEraserMode(value: ScreenshotEraserMode) {
size="small"
hide-checkmark-icon
/>
-
-
-
-
+ :model-value="color"
+ :label="formatMessage(messages.colour)"
+ @update:model-value="updateColor"
+ @focus="beginPropertyEdit"
+ @change="commitPropertyEdit"
+ />
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {
+ render: (args) => ({
+ components: { ColorPicker },
+ setup() {
+ const value = ref('#ff6b6b')
+ return { args, value }
+ },
+ template: /* html */ `
+
+
+
Selected value: {{ value }}
+
+ `,
+ }),
+ args: {
+ label: 'Colour',
+ },
+}
+
+export const Small: Story = {
+ render: () => ({
+ components: { ColorPicker },
+ setup() {
+ const value = ref('#3b82f6')
+ return { value }
+ },
+ template: /* html */ `
+
+
+
Selected value: {{ value }}
+
+ `,
+ }),
+}
+
+export const Disabled: Story = {
+ args: {
+ label: 'Colour',
+ modelValue: '#22c55e',
+ disabled: true,
+ },
+}
+
+export const FocusAndChangeEvents: Story = {
+ render: () => ({
+ components: { ColorPicker },
+ setup() {
+ const value = ref('#a855f7')
+ const log = ref([])
+ return { value, log }
+ },
+ template: /* html */ `
+
+
log.unshift('change: committed ' + next)"
+ />
+
+
+ `,
+ }),
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c58967540c..1d81ae72f2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -185,7 +185,7 @@ importers:
devDependencies:
'@eslint/compat':
specifier: ^1.1.1
- version: 1.4.1(eslint@9.39.2(jiti@1.21.7))
+ version: 1.4.1(eslint@9.39.2(jiti@2.6.1))
'@formatjs/cli':
specifier: ^6.2.12
version: 6.12.2(@vue/compiler-core@3.5.27)(vue@3.5.27(typescript@5.9.3))
@@ -194,22 +194,22 @@ importers:
version: link:../../packages/tooling-config
'@nuxt/eslint-config':
specifier: ^0.5.6
- version: 0.5.7(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
+ version: 0.5.7(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
'@taijased/vue-render-tracker':
specifier: ^1.0.7
version: 1.0.7(vue@3.5.27(typescript@5.9.3))
'@vitejs/plugin-vue':
specifier: ^6.0.3
- version: 6.0.4(vite@8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
+ version: 6.0.4(vite@8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
autoprefixer:
specifier: ^10.4.19
version: 10.4.24(postcss@8.5.6)
eslint:
specifier: ^9.9.1
- version: 9.39.2(jiti@1.21.7)
+ version: 9.39.2(jiti@2.6.1)
eslint-plugin-turbo:
specifier: ^2.5.4
- version: 2.8.2(eslint@9.39.2(jiti@1.21.7))(turbo@2.8.2)
+ version: 2.8.2(eslint@9.39.2(jiti@2.6.1))(turbo@2.8.2)
postcss:
specifier: ^8.4.39
version: 8.5.6
@@ -227,7 +227,7 @@ importers:
version: 5.9.3
vite:
specifier: ^8.0.0
- version: 8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
+ version: 8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
vue-component-type-helpers:
specifier: ^3.1.8
version: 3.2.4
@@ -712,6 +712,9 @@ importers:
apexcharts:
specifier: ^4.0.0
version: 4.7.0
+ colord:
+ specifier: ^2.9.3
+ version: 2.9.3
dayjs:
specifier: ^1.11.10
version: 1.11.19
@@ -721,12 +724,12 @@ importers:
es-toolkit:
specifier: ^1.44.0
version: 1.44.0
- flatpickr:
- specifier: ^4.6.13
- version: 4.6.13
fabric:
specifier: ^7.4.0
version: 7.4.0
+ flatpickr:
+ specifier: ^4.6.13
+ version: 4.6.13
floating-vue:
specifier: ^5.2.2
version: 5.2.2(@nuxt/kit@3.21.0(magicast@0.5.1))(vue@3.5.27(typescript@5.9.3))
@@ -811,7 +814,7 @@ importers:
version: 5.2.4(vite@5.4.21(@types/node@24.12.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0))(vue@3.5.27(typescript@5.9.3))
eslint-plugin-storybook:
specifier: ^10.1.10
- version: 10.2.4(eslint@9.39.2(jiti@2.6.1))(storybook@10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@5.9.3)
+ version: 10.2.4(eslint@9.39.2(jiti@1.21.7))(storybook@10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@5.9.3)
storybook:
specifier: ^10.1.10
version: 10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -6443,6 +6446,7 @@ packages:
eslint@9.39.2:
resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
peerDependencies:
jiti: '*'
@@ -11807,12 +11811,6 @@ snapshots:
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/compat@1.4.1(eslint@9.39.2(jiti@1.21.7))':
- dependencies:
- '@eslint/core': 0.17.0
- optionalDependencies:
- eslint: 9.39.2(jiti@1.21.7)
-
'@eslint/compat@1.4.1(eslint@9.39.2(jiti@2.6.1))':
dependencies:
'@eslint/core': 0.17.0
@@ -12581,31 +12579,6 @@ snapshots:
- utf-8-validate
- vue
- '@nuxt/eslint-config@0.5.7(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
- dependencies:
- '@eslint/js': 9.39.2
- '@nuxt/eslint-plugin': 0.5.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- '@stylistic/eslint-plugin': 2.13.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- eslint: 9.39.2(jiti@1.21.7)
- eslint-config-flat-gitignore: 0.3.0(eslint@9.39.2(jiti@1.21.7))
- eslint-flat-config-utils: 0.4.0
- eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))
- eslint-plugin-jsdoc: 50.8.0(eslint@9.39.2(jiti@1.21.7))
- eslint-plugin-regexp: 2.10.0(eslint@9.39.2(jiti@1.21.7))
- eslint-plugin-unicorn: 55.0.0(eslint@9.39.2(jiti@1.21.7))
- eslint-plugin-vue: 9.33.0(eslint@9.39.2(jiti@1.21.7))
- globals: 15.15.0
- local-pkg: 0.5.1
- pathe: 1.1.2
- vue-eslint-parser: 9.4.3(eslint@9.39.2(jiti@1.21.7))
- transitivePeerDependencies:
- - '@typescript-eslint/utils'
- - eslint-import-resolver-node
- - supports-color
- - typescript
-
'@nuxt/eslint-config@0.5.7(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint/js': 9.39.2
@@ -12631,15 +12604,6 @@ snapshots:
- supports-color
- typescript
- '@nuxt/eslint-plugin@0.5.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/types': 8.54.0
- '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- eslint: 9.39.2(jiti@1.21.7)
- transitivePeerDependencies:
- - supports-color
- - typescript
-
'@nuxt/eslint-plugin@0.5.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.54.0
@@ -14132,18 +14096,6 @@ snapshots:
'@stripe/stripe-js@7.9.0': {}
- '@stylistic/eslint-plugin@2.13.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- eslint: 9.39.2(jiti@1.21.7)
- eslint-visitor-keys: 4.2.1
- espree: 10.4.0
- estraverse: 5.3.0
- picomatch: 4.0.4
- transitivePeerDependencies:
- - supports-color
- - typescript
-
'@stylistic/eslint-plugin@2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
@@ -14558,22 +14510,6 @@ snapshots:
dependencies:
'@types/node': 24.12.2
- '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
- dependencies:
- '@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- '@typescript-eslint/scope-manager': 8.54.0
- '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.54.0
- eslint: 9.39.2(jiti@1.21.7)
- ignore: 7.0.5
- natural-compare: 1.4.0
- ts-api-utils: 2.4.0(typescript@5.9.3)
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -14590,18 +14526,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/scope-manager': 8.54.0
- '@typescript-eslint/types': 8.54.0
- '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.54.0
- debug: 4.4.3
- eslint: 9.39.2(jiti@1.21.7)
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.54.0
@@ -14645,18 +14569,6 @@ snapshots:
dependencies:
typescript: 5.9.3
- '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/types': 8.54.0
- '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
- '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- debug: 4.4.3
- eslint: 9.39.2(jiti@1.21.7)
- ts-api-utils: 2.4.0(typescript@5.9.3)
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.54.0
@@ -14849,12 +14761,6 @@ snapshots:
vite: 7.3.1(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
vue: 3.5.27(typescript@5.9.3)
- '@vitejs/plugin-vue@6.0.4(vite@8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))':
- dependencies:
- '@rolldown/pluginutils': 1.0.0-rc.2
- vite: 8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)
- vue: 3.5.27(typescript@5.9.3)
-
'@vitejs/plugin-vue@6.0.4(vite@8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.2
@@ -16471,12 +16377,6 @@ snapshots:
escape-string-regexp@5.0.0: {}
- eslint-config-flat-gitignore@0.3.0(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- '@eslint/compat': 1.4.1(eslint@9.39.2(jiti@1.21.7))
- eslint: 9.39.2(jiti@1.21.7)
- find-up-simple: 1.0.1
-
eslint-config-flat-gitignore@0.3.0(eslint@9.39.2(jiti@2.6.1)):
dependencies:
'@eslint/compat': 1.4.1(eslint@9.39.2(jiti@2.6.1))
@@ -16498,23 +16398,6 @@ snapshots:
optionalDependencies:
unrs-resolver: 1.11.1
- eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- '@typescript-eslint/types': 8.54.0
- comment-parser: 1.4.5
- debug: 4.4.3
- eslint: 9.39.2(jiti@1.21.7)
- eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
- is-glob: 4.0.3
- minimatch: 10.1.2
- semver: 7.7.4
- stable-hash-x: 0.2.0
- unrs-resolver: 1.11.1
- optionalDependencies:
- '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
- transitivePeerDependencies:
- - supports-color
-
eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
dependencies:
'@typescript-eslint/types': 8.54.0
@@ -16532,22 +16415,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-jsdoc@50.8.0(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- '@es-joy/jsdoccomment': 0.50.2
- are-docs-informative: 0.0.2
- comment-parser: 1.4.1
- debug: 4.4.3
- escape-string-regexp: 4.0.0
- eslint: 9.39.2(jiti@1.21.7)
- espree: 10.4.0
- esquery: 1.7.0
- parse-imports-exports: 0.2.4
- semver: 7.7.4
- spdx-expression-parse: 4.0.0
- transitivePeerDependencies:
- - supports-color
-
eslint-plugin-jsdoc@50.8.0(eslint@9.39.2(jiti@2.6.1)):
dependencies:
'@es-joy/jsdoccomment': 0.50.2
@@ -16573,17 +16440,6 @@ snapshots:
optionalDependencies:
eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1))
- eslint-plugin-regexp@2.10.0(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7))
- '@eslint-community/regexpp': 4.12.2
- comment-parser: 1.4.5
- eslint: 9.39.2(jiti@1.21.7)
- jsdoc-type-pratt-parser: 4.8.0
- refa: 0.12.1
- regexp-ast-analysis: 0.7.1
- scslre: 0.3.0
-
eslint-plugin-regexp@2.10.0(eslint@9.39.2(jiti@2.6.1)):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
@@ -16599,47 +16455,21 @@ snapshots:
dependencies:
eslint: 9.39.2(jiti@2.6.1)
- eslint-plugin-storybook@10.2.4(eslint@9.39.2(jiti@2.6.1))(storybook@10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@5.9.3):
+ eslint-plugin-storybook@10.2.4(eslint@9.39.2(jiti@1.21.7))(storybook@10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@5.9.3):
dependencies:
- '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- eslint: 9.39.2(jiti@2.6.1)
+ '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
+ eslint: 9.39.2(jiti@1.21.7)
storybook: 10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
transitivePeerDependencies:
- supports-color
- typescript
- eslint-plugin-turbo@2.8.2(eslint@9.39.2(jiti@1.21.7))(turbo@2.8.2):
- dependencies:
- dotenv: 16.0.3
- eslint: 9.39.2(jiti@1.21.7)
- turbo: 2.8.2
-
eslint-plugin-turbo@2.8.2(eslint@9.39.2(jiti@2.6.1))(turbo@2.8.2):
dependencies:
dotenv: 16.0.3
eslint: 9.39.2(jiti@2.6.1)
turbo: 2.8.2
- eslint-plugin-unicorn@55.0.0(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- '@babel/helper-validator-identifier': 7.28.5
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7))
- ci-info: 4.4.0
- clean-regexp: 1.0.0
- core-js-compat: 3.48.0
- eslint: 9.39.2(jiti@1.21.7)
- esquery: 1.7.0
- globals: 15.15.0
- indent-string: 4.0.0
- is-builtin-module: 3.2.1
- jsesc: 3.1.0
- pluralize: 8.0.0
- read-pkg-up: 7.0.1
- regexp-tree: 0.1.27
- regjsparser: 0.10.0
- semver: 7.7.4
- strip-indent: 3.0.0
-
eslint-plugin-unicorn@55.0.0(eslint@9.39.2(jiti@2.6.1)):
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -16674,20 +16504,6 @@ snapshots:
'@stylistic/eslint-plugin': 2.13.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- eslint-plugin-vue@9.33.0(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7))
- eslint: 9.39.2(jiti@1.21.7)
- globals: 13.24.0
- natural-compare: 1.4.0
- nth-check: 2.1.1
- postcss-selector-parser: 6.1.2
- semver: 7.7.4
- vue-eslint-parser: 9.4.3(eslint@9.39.2(jiti@1.21.7))
- xml-name-validator: 4.0.0
- transitivePeerDependencies:
- - supports-color
-
eslint-plugin-vue@9.33.0(eslint@9.39.2(jiti@2.6.1)):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
@@ -21399,22 +21215,6 @@ snapshots:
yaml: 2.8.2
optional: true
- vite@8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@1.21.7)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2):
- dependencies:
- lightningcss: 1.32.0
- picomatch: 4.0.4
- postcss: 8.5.8
- rolldown: 1.0.0-rc.12
- tinyglobby: 0.2.15
- optionalDependencies:
- '@types/node': 24.12.2
- esbuild: 0.27.3
- fsevents: 2.3.3
- jiti: 1.21.7
- sass: 1.97.3
- terser: 5.46.0
- yaml: 2.8.2
-
vite@8.0.3(@types/node@24.12.2)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2):
dependencies:
lightningcss: 1.32.0
@@ -21591,19 +21391,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vue-eslint-parser@9.4.3(eslint@9.39.2(jiti@1.21.7)):
- dependencies:
- debug: 4.4.3
- eslint: 9.39.2(jiti@1.21.7)
- eslint-scope: 7.2.2
- eslint-visitor-keys: 3.4.3
- espree: 9.6.1
- esquery: 1.7.0
- lodash: 4.17.23
- semver: 7.7.4
- transitivePeerDependencies:
- - supports-color
-
vue-eslint-parser@9.4.3(eslint@9.39.2(jiti@2.6.1)):
dependencies:
debug: 4.4.3
diff --git a/standards/frontend/INPUTS.md b/standards/frontend/INPUTS.md
index fa5c48fb91..959b063d91 100644
--- a/standards/frontend/INPUTS.md
+++ b/standards/frontend/INPUTS.md
@@ -12,12 +12,13 @@ into application code.
## Select a component
-| Component | Use |
-| ------------ | -------------------------------------------------------------------------- |
-| `Input` | Single-line text, email, password, number, URL, or search entry |
-| `Textarea` | Multi-line text entry |
-| `DateInput` | A native browser control for a date or a local date and time |
-| `DatePicker` | A styled calendar for ranges, multiple dates, time, or inline presentation |
+| Component | Use |
+| ------------- | -------------------------------------------------------------------------- |
+| `Input` | Single-line text, email, password, number, URL, or search entry |
+| `Textarea` | Multi-line text entry |
+| `DateInput` | A native browser control for a date or a local date and time |
+| `DatePicker` | A styled calendar for ranges, multiple dates, time, or inline presentation |
+| `ColorPicker` | A swatch button that opens a hue and saturation picker with a hex field |
Use a dedicated component for each other type of control. Do not use `Input` instead
of a select, checkbox, radio group, toggle, or file picker.
@@ -26,7 +27,7 @@ Import the public input components from `@modrinth/ui`. In `packages/ui`, import
from `#ui/components/base/inputs`:
```ts
-import { DateInput, DatePicker, Input, Textarea } from '@modrinth/ui'
+import { ColorPicker, DateInput, DatePicker, Input, Textarea } from '@modrinth/ui'
```
## Labels and field descriptions
@@ -38,12 +39,7 @@ For a visible label, set the same value for the label `for` and the control `id`
```vue
Project name
-
+
```
An input does not have a `label` prop. Do not use a placeholder as the only label. The
@@ -52,13 +48,7 @@ placeholder is not visible after the user enters a value.
If a visible label repeats other content, use an accessible name such as `aria-label`:
```vue
-
+
```
Connect descriptions and validation messages to the control with `aria-describedby`.
@@ -66,12 +56,7 @@ The component sends additional attributes to the native control:
```vue
Project URL
-
+
Use lowercase letters, numbers, and dashes.
{{ slugError }}
@@ -87,22 +72,9 @@ Bind the field value with `v-model`. For text types, the `Input` model is a stri
`type="number"`, the model is a number or `undefined`:
```vue
-
-
-
+
+
+
```
An empty number input sets the model to `undefined`. The `clamp` prop keeps the number
@@ -117,14 +89,7 @@ the native input type are different.
Use `Textarea` for multi-line content:
```vue
-
+
```
`Textarea` has three rows by default. The default value of `resize` is `none`. The other
@@ -145,8 +110,8 @@ nameInput.value?.focus()
`Input`, `Textarea`, and `DateInput` have the same three appearances:
-| Appearance | Use |
-| ------------- | ------------------------------------------------------------------- |
+| Appearance | Use |
+| ------------- | ------------------------------------------------------------------ |
| `surface` | Standard fields on pages, cards, and modals. This is the default. |
| `button` | Compact fields in panels that use the button surface. |
| `transparent` | Fields in a container that already shows the visible field border. |
@@ -195,12 +160,7 @@ standard input height.
Set `icon` on `Input` to show a decorative leading icon:
```vue
-
+
```
Use the `leading` slot for other decorative content. The component hides this content
@@ -245,13 +205,7 @@ The clear button is visible only when the field has a value. It is not visible w
field is disabled or readonly:
```vue
-
+
```
For `Input` and `DateInput`, set a localized `clear-label`. This label is the accessible
@@ -285,13 +239,7 @@ Set `error` after validation finds an invalid value. `Input`, `Textarea`, and
control:
```vue
-
+
{{ websiteError }}
```
@@ -314,15 +262,7 @@ string:
```vue
Scheduled date
-
+
```
Set `min`, `max`, and `step` for native limits. The calendar button gives focus to the
@@ -344,21 +284,9 @@ functions:
selections as formatted strings:
```vue
-
-
-
+
+
+
```
The modes use these model types:
@@ -376,13 +304,7 @@ is set.
Set `date-format` and `alt-format` to use other Flatpickr formats:
```vue
-
+
```
Make sure that model strings agree with `date-format`. The `alt-format` prop changes
@@ -413,16 +335,41 @@ The component sends a `clamp` event when it uses a different day. Use this event
interface must explain the change:
```vue
-
+
```
The picker has `focus()`, `open()`, `close()`, and `clear()` methods. Use these methods
only when a workflow requires control from the parent component.
+## Color picker
+
+`ColorPicker` shows a swatch button. It opens a popup with a saturation and
+brightness area, a hue slider, and a hex field. Its model is a 6-digit hex string
+such as `#ff6b6b`:
+
+```vue
+
+```
+
+Set a localized `label`. It is the accessible name of the swatch button and the popup,
+and it is the content of the button's tooltip.
+
+`ColorPicker` has two sizes for the swatch button: `md` (the default, 36 px) and `sm`
+(32 px). It does not use the shared `InputSize` scale, because the button is a square
+swatch rather than a text field.
+
+The component sends a `focus` event when the popup opens and a `change` event with the
+final value when the popup closes. Use these events to group the edits made while the
+popup is open into a single undo step, the way a native ` ` groups
+edits into one `change`:
+
+```vue
+
+```
+
+Set `disabled` to prevent changes. The picker has `open()` and `close()` methods. Use
+these methods only when a workflow requires control from the parent component.
+
## Classes and native attributes
`Input`, `Textarea`, and `DateInput` send additional attributes to the native control.