From 13ff72e5e3ced3810f754ff2a02c50d345053f5f Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 12:52:35 +1000 Subject: [PATCH 1/9] feat: add color swatches --- docs/.vitepress/config.ts | 10 ++ .../components/demo/ColorSwatch/css/index.vue | 11 ++ .../demo/ColorSwatch/css/styles.css | 11 ++ .../demo/ColorSwatch/tailwind/index.vue | 10 ++ .../ColorSwatch/tailwind/tailwind.config.js | 6 + .../demo/ColorSwatchPicker/css/index.vue | 31 ++++ .../demo/ColorSwatchPicker/css/styles.css | 32 +++++ .../demo/ColorSwatchPicker/tailwind/index.vue | 30 ++++ .../tailwind/tailwind.config.js | 6 + .../docs/components/color-swatch-picker.md | 90 ++++++++++++ docs/content/docs/components/color-swatch.md | 58 ++++++++ docs/content/meta/ColorSwatch.md | 25 ++++ docs/content/meta/ColorSwatchPickerItem.md | 37 +++++ .../meta/ColorSwatchPickerItemIndicator.md | 17 +++ docs/content/meta/ColorSwatchPickerRoot.md | 112 +++++++++++++++ packages/core/constant/components.ts | 11 ++ .../core/src/ColorSwatch/ColorSwatch.test.ts | 25 ++++ packages/core/src/ColorSwatch/ColorSwatch.vue | 61 ++++++++ packages/core/src/ColorSwatch/index.ts | 4 + .../ColorSwatchPicker.test.ts | 29 ++++ .../ColorSwatchPickerItem.vue | 44 ++++++ .../ColorSwatchPickerItemIndicator.vue | 17 +++ .../ColorSwatchPickerItemSwatch.vue | 21 +++ .../ColorSwatchPickerRoot.vue | 49 +++++++ packages/core/src/ColorSwatchPicker/index.ts | 17 +++ packages/core/src/color/index.ts | 1 + packages/core/src/color/utils.ts | 132 ++++++++++++++++++ packages/core/src/index.ts | 2 + 28 files changed, 899 insertions(+) create mode 100644 docs/components/demo/ColorSwatch/css/index.vue create mode 100644 docs/components/demo/ColorSwatch/css/styles.css create mode 100644 docs/components/demo/ColorSwatch/tailwind/index.vue create mode 100644 docs/components/demo/ColorSwatch/tailwind/tailwind.config.js create mode 100644 docs/components/demo/ColorSwatchPicker/css/index.vue create mode 100644 docs/components/demo/ColorSwatchPicker/css/styles.css create mode 100644 docs/components/demo/ColorSwatchPicker/tailwind/index.vue create mode 100644 docs/components/demo/ColorSwatchPicker/tailwind/tailwind.config.js create mode 100644 docs/content/docs/components/color-swatch-picker.md create mode 100644 docs/content/docs/components/color-swatch.md create mode 100644 docs/content/meta/ColorSwatch.md create mode 100644 docs/content/meta/ColorSwatchPickerItem.md create mode 100644 docs/content/meta/ColorSwatchPickerItemIndicator.md create mode 100644 docs/content/meta/ColorSwatchPickerRoot.md create mode 100644 packages/core/src/ColorSwatch/ColorSwatch.test.ts create mode 100644 packages/core/src/ColorSwatch/ColorSwatch.vue create mode 100644 packages/core/src/ColorSwatch/index.ts create mode 100644 packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts create mode 100644 packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue create mode 100644 packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemIndicator.vue create mode 100644 packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemSwatch.vue create mode 100644 packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue create mode 100644 packages/core/src/ColorSwatchPicker/index.ts create mode 100644 packages/core/src/color/index.ts create mode 100644 packages/core/src/color/utils.ts diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 6a01653e49..328ed332e5 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -157,6 +157,16 @@ export default defineConfig({ { text: 'Toggle Group', link: '/docs/components/toggle-group' }, ], }, + { + text: 'Color', + items: [ + { text: 'Color Swatch', link: '/docs/components/color-swatch' }, + { + text: 'Color Swatch Picker', + link: '/docs/components/color-swatch-picker', + }, + ], + }, { text: 'Dates', items: [ diff --git a/docs/components/demo/ColorSwatch/css/index.vue b/docs/components/demo/ColorSwatch/css/index.vue new file mode 100644 index 0000000000..613b1f12ba --- /dev/null +++ b/docs/components/demo/ColorSwatch/css/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/docs/components/demo/ColorSwatch/css/styles.css b/docs/components/demo/ColorSwatch/css/styles.css new file mode 100644 index 0000000000..1cf37ca441 --- /dev/null +++ b/docs/components/demo/ColorSwatch/css/styles.css @@ -0,0 +1,11 @@ +.swatch { + width: 32px; + height: 32px; + border-radius: 4px; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15); + flex-shrink: 0; +} + +.dark .swatch { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15); +} \ No newline at end of file diff --git a/docs/components/demo/ColorSwatch/tailwind/index.vue b/docs/components/demo/ColorSwatch/tailwind/index.vue new file mode 100644 index 0000000000..6895143cd5 --- /dev/null +++ b/docs/components/demo/ColorSwatch/tailwind/index.vue @@ -0,0 +1,10 @@ + + + diff --git a/docs/components/demo/ColorSwatch/tailwind/tailwind.config.js b/docs/components/demo/ColorSwatch/tailwind/tailwind.config.js new file mode 100644 index 0000000000..fe691bdd5f --- /dev/null +++ b/docs/components/demo/ColorSwatch/tailwind/tailwind.config.js @@ -0,0 +1,6 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./**/*.vue'], + theme: {}, + plugins: [], +} diff --git a/docs/components/demo/ColorSwatchPicker/css/index.vue b/docs/components/demo/ColorSwatchPicker/css/index.vue new file mode 100644 index 0000000000..ed6af2c068 --- /dev/null +++ b/docs/components/demo/ColorSwatchPicker/css/index.vue @@ -0,0 +1,31 @@ + + + diff --git a/docs/components/demo/ColorSwatchPicker/css/styles.css b/docs/components/demo/ColorSwatchPicker/css/styles.css new file mode 100644 index 0000000000..c927379b69 --- /dev/null +++ b/docs/components/demo/ColorSwatchPicker/css/styles.css @@ -0,0 +1,32 @@ +.root { + display: flex; + gap: 8px; +} + +.item { + position: relative; +} + +.swatch { + height: 32px; + width: 32px; + border-radius: 4px; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15); + flex-shrink: 0; + cursor: pointer; +} + +.dark .swatch { + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15); +} + +.indicator { + position: absolute; + bottom: 4px; + right: 4px; + color: white; +} + +.swatch[data-color-contrast='dark'] ~ .indicator { + color: black; +} \ No newline at end of file diff --git a/docs/components/demo/ColorSwatchPicker/tailwind/index.vue b/docs/components/demo/ColorSwatchPicker/tailwind/index.vue new file mode 100644 index 0000000000..5f69b3f81b --- /dev/null +++ b/docs/components/demo/ColorSwatchPicker/tailwind/index.vue @@ -0,0 +1,30 @@ + + + diff --git a/docs/components/demo/ColorSwatchPicker/tailwind/tailwind.config.js b/docs/components/demo/ColorSwatchPicker/tailwind/tailwind.config.js new file mode 100644 index 0000000000..fe691bdd5f --- /dev/null +++ b/docs/components/demo/ColorSwatchPicker/tailwind/tailwind.config.js @@ -0,0 +1,6 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./**/*.vue'], + theme: {}, + plugins: [], +} diff --git a/docs/content/docs/components/color-swatch-picker.md b/docs/content/docs/components/color-swatch-picker.md new file mode 100644 index 0000000000..5711006717 --- /dev/null +++ b/docs/content/docs/components/color-swatch-picker.md @@ -0,0 +1,90 @@ +--- + +title: Color Swatch Picker +description: A component that allows users to select from a set of predefined colors, often used for themes or branding. +name: color-swatch-picker +--- + +# Color Swatch Picker + + +A component that allows users to select from a set of predefined colors, often used for themes or branding. + + + + +## Features + + + +## Installation + +Install the component from your command line. + + + +## Anatomy + +Import all parts and piece them together. + +```vue + + + +``` + +## API Reference + +### ColorSwatchPickerRoot + +The main component that displays a color swatch picker. + + + +### ColorSwatchPickerItem + +The item that represents a selectable color swatch. + + + +### ColorSwatchPickerItemSwatch + +The component that displays the color swatch within an item. + + + +### ColorSwatchPickerItemIndicator + +The component that indicates the selected color swatch within an item. + + diff --git a/docs/content/docs/components/color-swatch.md b/docs/content/docs/components/color-swatch.md new file mode 100644 index 0000000000..7842d3303b --- /dev/null +++ b/docs/content/docs/components/color-swatch.md @@ -0,0 +1,58 @@ +--- + +title: Color Swatch +description: A predefined color block that allows users to quickly select commonly used or brand-specific colors. +name: color-swatch +--- + +# Color Swatch + + +Displays a color swatch, which can be used to represent colors in a UI. + + + + +## Features + + + +## Installation + +Install the component from your command line. + + + +## Anatomy + +Import all parts and piece them together. + +```vue + + + +``` + +## API Reference + +### ColorSwatch + +The main component that displays a color swatch. + + diff --git a/docs/content/meta/ColorSwatch.md b/docs/content/meta/ColorSwatch.md new file mode 100644 index 0000000000..afaf643915 --- /dev/null +++ b/docs/content/meta/ColorSwatch.md @@ -0,0 +1,25 @@ + + + + + diff --git a/docs/content/meta/ColorSwatchPickerItem.md b/docs/content/meta/ColorSwatchPickerItem.md new file mode 100644 index 0000000000..25f4d859b2 --- /dev/null +++ b/docs/content/meta/ColorSwatchPickerItem.md @@ -0,0 +1,37 @@ + + + + + diff --git a/docs/content/meta/ColorSwatchPickerItemIndicator.md b/docs/content/meta/ColorSwatchPickerItemIndicator.md new file mode 100644 index 0000000000..5df63e84f6 --- /dev/null +++ b/docs/content/meta/ColorSwatchPickerItemIndicator.md @@ -0,0 +1,17 @@ + + + diff --git a/docs/content/meta/ColorSwatchPickerRoot.md b/docs/content/meta/ColorSwatchPickerRoot.md new file mode 100644 index 0000000000..55c54316ae --- /dev/null +++ b/docs/content/meta/ColorSwatchPickerRoot.md @@ -0,0 +1,112 @@ + + + + + + + diff --git a/packages/core/constant/components.ts b/packages/core/constant/components.ts index e1f1ac71a8..80fa341e35 100644 --- a/packages/core/constant/components.ts +++ b/packages/core/constant/components.ts @@ -56,6 +56,17 @@ export const components = { 'CollapsibleContent', ] as const, + colorSwatch: [ + 'ColorSwatch', + ] as const, + + colorSwatchPicker: [ + 'ColorSwatchPickerRoot', + 'ColorSwatchPickerItem', + 'ColorSwatchPickerItemSwatch', + 'ColorSwatchPickerItemIndicator', + ] as const, + combobox: [ 'ComboboxRoot', 'ComboboxInput', diff --git a/packages/core/src/ColorSwatch/ColorSwatch.test.ts b/packages/core/src/ColorSwatch/ColorSwatch.test.ts new file mode 100644 index 0000000000..687415ad4f --- /dev/null +++ b/packages/core/src/ColorSwatch/ColorSwatch.test.ts @@ -0,0 +1,25 @@ +import { render } from '@testing-library/vue' +import { describe, expect, it } from 'vitest' +import ColorSwatch from './ColorSwatch.vue' + +const TEST_COLOR = '#123abc' + +describe('colorSwatch.vue', () => { + it('applies the correct style variable for color', () => { + const { getByRole } = render(ColorSwatch, { props: { color: TEST_COLOR } }) + const swatch = getByRole('img') + expect(swatch.style.getPropertyValue('--reka-color-swatch-color')).toBe(TEST_COLOR) + }) + + it('sets the correct aria-label', () => { + const { getByRole } = render(ColorSwatch, { props: { color: TEST_COLOR } }) + const swatch = getByRole('img') + expect(swatch.hasAttribute('aria-label')).toBe(true) + }) + + it('sets aria-roledescription to "color swatch"', () => { + const { getByRole } = render(ColorSwatch, { props: { color: TEST_COLOR } }) + const swatch = getByRole('img') + expect(swatch.getAttribute('aria-roledescription')).toBe('color swatch') + }) +}) diff --git a/packages/core/src/ColorSwatch/ColorSwatch.vue b/packages/core/src/ColorSwatch/ColorSwatch.vue new file mode 100644 index 0000000000..2cfcd76167 --- /dev/null +++ b/packages/core/src/ColorSwatch/ColorSwatch.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/packages/core/src/ColorSwatch/index.ts b/packages/core/src/ColorSwatch/index.ts new file mode 100644 index 0000000000..db424fa21e --- /dev/null +++ b/packages/core/src/ColorSwatch/index.ts @@ -0,0 +1,4 @@ +export { + default as ColorSwatch, + type ColorSwatchProps, +} from './ColorSwatch.vue' diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts b/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts new file mode 100644 index 0000000000..8f4bfff217 --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts @@ -0,0 +1,29 @@ +import { render } from '@testing-library/vue' +import { describe, expect, it } from 'vitest' +import ColorSwatchPickerItem from './ColorSwatchPickerItem.vue' +import ColorSwatchPickerRoot from './ColorSwatchPickerRoot.vue' + +const TEST_COLOR = '#ff00aa' + +describe('colorSwatchPickerRoot.vue', () => { + it('renders with the correct model value', () => { + const { getByRole } = render(ColorSwatchPickerRoot, { + props: { modelValue: TEST_COLOR }, + slots: { + default: `
`, + }, + }) + expect(getByRole('listbox')).toBeInTheDocument() + }) +}) + +describe('colorSwatchPickerItem.vue', () => { + it('applies the correct style variable for color', () => { + const { getByRole } = render(ColorSwatchPickerItem, { + props: { value: TEST_COLOR }, + slots: { default: 'item' }, + }) + const item = getByRole('option') + expect(item.style.getPropertyValue('--reka-color-swatch-picker-item-color')).toBe(TEST_COLOR) + }) +}) diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue new file mode 100644 index 0000000000..27b3f660ae --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemIndicator.vue b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemIndicator.vue new file mode 100644 index 0000000000..7294b6935e --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemIndicator.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemSwatch.vue b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemSwatch.vue new file mode 100644 index 0000000000..189daffa4d --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItemSwatch.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue new file mode 100644 index 0000000000..81aeb9db40 --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/packages/core/src/ColorSwatchPicker/index.ts b/packages/core/src/ColorSwatchPicker/index.ts new file mode 100644 index 0000000000..9320ca4703 --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/index.ts @@ -0,0 +1,17 @@ +export { + default as ColorSwatchPickerItem, + type ColorSwatchPickerItemProps, +} from './ColorSwatchPickerItem.vue' +export { + default as ColorSwatchPickerItemIndicator, + type ColorSwatchPickerItemIndicatorProps, +} from './ColorSwatchPickerItemIndicator.vue' +export { + default as ColorSwatchPickerItemSwatch, + type ColorSwatchPickerItemSwatchProps, +} from './ColorSwatchPickerItemSwatch.vue' +export { + default as ColorSwatchPickerRoot, + type ColorSwatchPickerRootEmits, + type ColorSwatchPickerRootProps, +} from './ColorSwatchPickerRoot.vue' diff --git a/packages/core/src/color/index.ts b/packages/core/src/color/index.ts new file mode 100644 index 0000000000..9c56149efa --- /dev/null +++ b/packages/core/src/color/index.ts @@ -0,0 +1 @@ +export * from './utils' diff --git a/packages/core/src/color/utils.ts b/packages/core/src/color/utils.ts new file mode 100644 index 0000000000..a475a6650a --- /dev/null +++ b/packages/core/src/color/utils.ts @@ -0,0 +1,132 @@ +/** + * Converts a hex color string to HSL (Hue, Saturation, Lightness). + * @param hex Hex color string (e.g., "#ff5733") + * @returns An object containing hue, saturation, and lightness values. + */ +export function hexToHSL(hex: string): { h: number, s: number, l: number } { + // Remove the hash at the start if it's there + hex = hex.replace(/^#/, '') + + // Parse the r, g, b values from the hex string + const bigint = parseInt(hex, 16) + let r = (bigint >> 16) & 255 + let g = (bigint >> 8) & 255 + let b = bigint & 255 + + // Convert to HSL + r /= 255 + g /= 255 + b /= 255 + + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + let h: number + let s: number + let l: number = (max + min) / 2 + + if (max === min) { + h = s = 0 // achromatic + } + else { + const d = max - min + s = l > 0.5 ? d / (2 - max - min) : d / (max + min) + if (max === r) { + h = (g - b) / d + (g < b ? 6 : 0) + } + else if (max === g) { + h = (b - r) / d + 2 + } + else { + h = (r - g) / d + 4 + } + h /= 6 + h *= 360 + s *= 100 + l *= 100 + } + + return { h, s, l } +} + +/** + * Converts a hex color string to a human-readable color name. + * @param hex Hex color string (e.g., "#ff5733") + * @returns A human-readable color name based on the hue, saturation, and lightness. + */ +export function getColorName(hex: string) { + // Convert RGB to HSL + const { h, s, l } = hexToHSL(hex) + + // Handle achromatic colors (low saturation) + if (s < 0.1) { + if (l < 0.1) + return 'black' + if (l > 0.95) + return 'white' + if (l < 0.2) + return 'very dark gray' + if (l < 0.35) + return 'dark gray' + if (l < 0.65) + return 'gray' + if (l < 0.8) + return 'light gray' + return 'very light gray' + } + + // Determine base color by hue + let baseName + if (h < 15 || h >= 345) + baseName = 'red' + else if (h < 45) + baseName = 'orange' + else if (h < 75) + baseName = 'yellow' + else if (h < 105) + baseName = 'yellow-green' + else if (h < 135) + baseName = 'green' + else if (h < 165) + baseName = 'green-cyan' + else if (h < 195) + baseName = 'cyan' + else if (h < 225) + baseName = 'blue' + else if (h < 255) + baseName = 'blue-violet' + else if (h < 285) + baseName = 'violet' + else if (h < 315) + baseName = 'magenta' + else baseName = 'red-magenta' + + // Add descriptors based on saturation and lightness + const descriptors = [] + if (s > 0.8) + descriptors.push('vibrant') + else if (s < 0.3) + descriptors.push('muted') + + if (l > 0.8) + descriptors.push('light') + else if (l < 0.3) + descriptors.push('dark') + + return descriptors.length > 0 + ? `${descriptors.join(' ')} ${baseName}` + : baseName +} + +export function getColorContrast(hex: string): 'light' | 'dark' { + // Convert hex to RGB + const bigint = parseInt(hex.replace(/^#/, ''), 16) + const r = (bigint >> 16) & 255 + const g = (bigint >> 8) & 255 + const b = bigint & 255 + + // Calculate luminance + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255 + + // Return 'light' or 'dark' based on luminance + return luminance > 0.5 ? 'dark' : 'light' +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 348691136c..5546c9b3f7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,6 +5,8 @@ export * from './Avatar' export * from './Calendar' export * from './Checkbox' export * from './Collapsible' +export * from './ColorSwatch' +export * from './ColorSwatchPicker' export * from './Combobox' // utilities export * from './ConfigProvider' From 939adf2366ff46ae81977ed510c170db1fc649ef Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:07:03 +1000 Subject: [PATCH 2/9] feat: use radix colors --- .../components/demo/ColorSwatch/css/index.vue | 3 ++- .../demo/ColorSwatch/tailwind/index.vue | 3 ++- .../demo/ColorSwatchPicker/css/index.vue | 21 ++++++++++++++----- .../demo/ColorSwatchPicker/tailwind/index.vue | 21 ++++++++++++++----- docs/content/docs/components/color-swatch.md | 8 ++----- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/components/demo/ColorSwatch/css/index.vue b/docs/components/demo/ColorSwatch/css/index.vue index 613b1f12ba..b0413f57b7 100644 --- a/docs/components/demo/ColorSwatch/css/index.vue +++ b/docs/components/demo/ColorSwatch/css/index.vue @@ -1,11 +1,12 @@ diff --git a/docs/components/demo/ColorSwatch/tailwind/index.vue b/docs/components/demo/ColorSwatch/tailwind/index.vue index 6895143cd5..89f9048993 100644 --- a/docs/components/demo/ColorSwatch/tailwind/index.vue +++ b/docs/components/demo/ColorSwatch/tailwind/index.vue @@ -1,10 +1,11 @@ diff --git a/docs/components/demo/ColorSwatchPicker/css/index.vue b/docs/components/demo/ColorSwatchPicker/css/index.vue index ed6af2c068..67a8f1d194 100644 --- a/docs/components/demo/ColorSwatchPicker/css/index.vue +++ b/docs/components/demo/ColorSwatchPicker/css/index.vue @@ -1,14 +1,25 @@ diff --git a/docs/components/demo/ColorSwatchPicker/tailwind/index.vue b/docs/components/demo/ColorSwatchPicker/tailwind/index.vue index 5f69b3f81b..04e26a4d25 100644 --- a/docs/components/demo/ColorSwatchPicker/tailwind/index.vue +++ b/docs/components/demo/ColorSwatchPicker/tailwind/index.vue @@ -1,13 +1,24 @@ diff --git a/docs/content/docs/components/color-swatch.md b/docs/content/docs/components/color-swatch.md index 7842d3303b..e91c869807 100644 --- a/docs/content/docs/components/color-swatch.md +++ b/docs/content/docs/components/color-swatch.md @@ -17,13 +17,9 @@ Displays a color swatch, which can be used to represent colors in a UI. From bd0be40f0c9c569143f5d9a5f7429e76ae4b802b Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:08:49 +1000 Subject: [PATCH 3/9] feat: update docs --- .../docs/components/color-swatch-picker.md | 2 +- docs/content/meta/ColorSwatch.md | 13 +++++++++++ .../meta/ColorSwatchPickerItemSwatch.md | 23 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 docs/content/meta/ColorSwatchPickerItemSwatch.md diff --git a/docs/content/docs/components/color-swatch-picker.md b/docs/content/docs/components/color-swatch-picker.md index 5711006717..9bcce6b227 100644 --- a/docs/content/docs/components/color-swatch-picker.md +++ b/docs/content/docs/components/color-swatch-picker.md @@ -40,8 +40,8 @@ Import all parts and piece them together. import { ColorSwatchPickerItem, ColorSwatchPickerItemIndicator, + ColorSwatchPickerItemSwatch, ColorSwatchPickerRoot, - ColorSwatchPickerSwatch } from 'reka-ui' diff --git a/docs/content/meta/ColorSwatch.md b/docs/content/meta/ColorSwatch.md index afaf643915..cecff1f6da 100644 --- a/docs/content/meta/ColorSwatch.md +++ b/docs/content/meta/ColorSwatch.md @@ -1,6 +1,19 @@ The element or component this component should render as. Can be overwritten by asChild.

\n', + 'type': 'AsTag | Component', + 'required': false, + 'default': '\'div\'' + }, + { + 'name': 'asChild', + 'description': '

Change the default rendered element for the one passed as a child, merging their props and behavior.

\n

Read our Composition guide for more details.

\n', + 'type': 'boolean', + 'required': false + }, + { + 'name': 'label', + 'description': '', + 'type': 'string', + 'required': false + } +]" /> From 10f991273579f7945195ecbe91719a2b41fa919e Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:11:50 +1000 Subject: [PATCH 4/9] feat: docs --- docs/content/meta/ColorSwatch.md | 4 ++-- docs/content/meta/ColorSwatchPickerItem.md | 2 +- docs/content/meta/ColorSwatchPickerItemSwatch.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/meta/ColorSwatch.md b/docs/content/meta/ColorSwatch.md index cecff1f6da..6ff30b9e57 100644 --- a/docs/content/meta/ColorSwatch.md +++ b/docs/content/meta/ColorSwatch.md @@ -16,14 +16,14 @@ }, { 'name': 'color', - 'description': '', + 'description': '

The color to display in the swatch as a hex string.\nExample: #16a372 or #ff5733.

\n', 'type': 'string', 'required': false, 'default': '\'\'' }, { 'name': 'label', - 'description': '', + 'description': '

Optional accessible label for the color. If omitted, the color name will be derived from the color value.

\n', 'type': 'string', 'required': false } diff --git a/docs/content/meta/ColorSwatchPickerItem.md b/docs/content/meta/ColorSwatchPickerItem.md index 25f4d859b2..965b0b595c 100644 --- a/docs/content/meta/ColorSwatchPickerItem.md +++ b/docs/content/meta/ColorSwatchPickerItem.md @@ -22,7 +22,7 @@ }, { 'name': 'value', - 'description': '

The value given as data when submitted with a name.

\n', + 'description': '

The color to display in the swatch as a hex string.\nExample: #16a372 or #ff5733.

\n', 'type': 'string', 'required': true } diff --git a/docs/content/meta/ColorSwatchPickerItemSwatch.md b/docs/content/meta/ColorSwatchPickerItemSwatch.md index 3dbd2c3504..701ee1d389 100644 --- a/docs/content/meta/ColorSwatchPickerItemSwatch.md +++ b/docs/content/meta/ColorSwatchPickerItemSwatch.md @@ -16,7 +16,7 @@ }, { 'name': 'label', - 'description': '', + 'description': '

Optional accessible label for the color. If omitted, the color name will be derived from the color value.

\n', 'type': 'string', 'required': false } From fdd294a98275ec1830a7bb5cb07104a0a7fb12dc Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:11:59 +1000 Subject: [PATCH 5/9] feat: comments --- packages/core/src/ColorSwatch/ColorSwatch.vue | 7 +++++++ .../core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/packages/core/src/ColorSwatch/ColorSwatch.vue b/packages/core/src/ColorSwatch/ColorSwatch.vue index 2cfcd76167..4d465c8ae9 100644 --- a/packages/core/src/ColorSwatch/ColorSwatch.vue +++ b/packages/core/src/ColorSwatch/ColorSwatch.vue @@ -2,7 +2,14 @@ import type { PrimitiveProps } from '@/Primitive' export interface ColorSwatchProps extends PrimitiveProps { + /** + * The color to display in the swatch as a hex string. + * Example: `#16a372` or `#ff5733`. + */ color?: string + /** + * Optional accessible label for the color. If omitted, the color name will be derived from the color value. + */ label?: string } diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue index 27b3f660ae..53ed0e3092 100644 --- a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerItem.vue @@ -4,6 +4,10 @@ import type { ListboxItemEmits, ListboxItemProps } from '@/Listbox' import { createContext, useForwardPropsEmits } from '@/shared' export interface ColorSwatchPickerItemProps extends ListboxItemProps { +/** + * The color to display in the swatch as a hex string. + * Example: `#16a372` or `#ff5733`. + */ value: string } From 7495c928950483139a0f0cef9965565aa25df0f1 Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:35:08 +1000 Subject: [PATCH 6/9] feat: tests --- .../core/src/ColorSwatch/ColorSwatch.test.ts | 29 ++++-------- .../src/ColorSwatch/story/_ColorSwatch.vue | 16 +++++++ .../ColorSwatchPicker.test.ts | 46 +++++++++---------- .../ColorSwatchPickerRoot.vue | 1 + .../story/_ColorSwatchPicker.vue | 37 +++++++++++++++ 5 files changed, 87 insertions(+), 42 deletions(-) create mode 100644 packages/core/src/ColorSwatch/story/_ColorSwatch.vue create mode 100644 packages/core/src/ColorSwatchPicker/story/_ColorSwatchPicker.vue diff --git a/packages/core/src/ColorSwatch/ColorSwatch.test.ts b/packages/core/src/ColorSwatch/ColorSwatch.test.ts index 687415ad4f..4a24d77de3 100644 --- a/packages/core/src/ColorSwatch/ColorSwatch.test.ts +++ b/packages/core/src/ColorSwatch/ColorSwatch.test.ts @@ -1,25 +1,16 @@ -import { render } from '@testing-library/vue' +import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' -import ColorSwatch from './ColorSwatch.vue' +import { axe } from 'vitest-axe' +import ColorSwatch from './story/_ColorSwatch.vue' -const TEST_COLOR = '#123abc' - -describe('colorSwatch.vue', () => { - it('applies the correct style variable for color', () => { - const { getByRole } = render(ColorSwatch, { props: { color: TEST_COLOR } }) - const swatch = getByRole('img') - expect(swatch.style.getPropertyValue('--reka-color-swatch-color')).toBe(TEST_COLOR) - }) - - it('sets the correct aria-label', () => { - const { getByRole } = render(ColorSwatch, { props: { color: TEST_COLOR } }) - const swatch = getByRole('img') - expect(swatch.hasAttribute('aria-label')).toBe(true) +describe('given a default ColorSwatch', () => { + it('should pass axe accessibility tests', async () => { + const wrapper = mount(ColorSwatch, { attachTo: document.body }) + expect(await axe(wrapper.element)).toHaveNoViolations() }) - it('sets aria-roledescription to "color swatch"', () => { - const { getByRole } = render(ColorSwatch, { props: { color: TEST_COLOR } }) - const swatch = getByRole('img') - expect(swatch.getAttribute('aria-roledescription')).toBe('color swatch') + it('should render ColorSwatch', () => { + const wrapper = mount(ColorSwatch) + expect(wrapper.element).toBeTruthy() }) }) diff --git a/packages/core/src/ColorSwatch/story/_ColorSwatch.vue b/packages/core/src/ColorSwatch/story/_ColorSwatch.vue new file mode 100644 index 0000000000..b9eb415a17 --- /dev/null +++ b/packages/core/src/ColorSwatch/story/_ColorSwatch.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts b/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts index 8f4bfff217..6798ee7ee5 100644 --- a/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPicker.test.ts @@ -1,29 +1,29 @@ -import { render } from '@testing-library/vue' -import { describe, expect, it } from 'vitest' -import ColorSwatchPickerItem from './ColorSwatchPickerItem.vue' -import ColorSwatchPickerRoot from './ColorSwatchPickerRoot.vue' +import type { DOMWrapper, VueWrapper } from '@vue/test-utils' +import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it } from 'vitest' +import { axe } from 'vitest-axe' +import ColorSwatchPicker from './story/_ColorSwatchPicker.vue' -const TEST_COLOR = '#ff00aa' +describe('given default ColorSwatchPicker', () => { + let wrapper: VueWrapper> + let content: DOMWrapper + let items: DOMWrapper[] -describe('colorSwatchPickerRoot.vue', () => { - it('renders with the correct model value', () => { - const { getByRole } = render(ColorSwatchPickerRoot, { - props: { modelValue: TEST_COLOR }, - slots: { - default: `
`, - }, - }) - expect(getByRole('listbox')).toBeInTheDocument() + beforeEach(() => { + document.body.innerHTML = '' + wrapper = mount(ColorSwatchPicker, { attachTo: document.body }) + content = wrapper.find('[role=listbox]') + items = wrapper.findAll('[role=option]') + }) + + it('should render the component', () => { + expect(wrapper.exists()).toBe(true) + expect(content.exists()).toBe(true) + expect(items.length).toBeGreaterThan(0) }) -}) -describe('colorSwatchPickerItem.vue', () => { - it('applies the correct style variable for color', () => { - const { getByRole } = render(ColorSwatchPickerItem, { - props: { value: TEST_COLOR }, - slots: { default: 'item' }, - }) - const item = getByRole('option') - expect(item.style.getPropertyValue('--reka-color-swatch-picker-item-color')).toBe(TEST_COLOR) + it('should have no accessibility violations', async () => { + const results = await axe(wrapper.element) + expect(results).toHaveNoViolations() }) }) diff --git a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue index 81aeb9db40..42d3423560 100644 --- a/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue +++ b/packages/core/src/ColorSwatchPicker/ColorSwatchPickerRoot.vue @@ -40,6 +40,7 @@ const forwarded = useForwardPropsEmits(props, emits) as-child > diff --git a/packages/core/src/ColorSwatchPicker/story/_ColorSwatchPicker.vue b/packages/core/src/ColorSwatchPicker/story/_ColorSwatchPicker.vue new file mode 100644 index 0000000000..65f562fc6f --- /dev/null +++ b/packages/core/src/ColorSwatchPicker/story/_ColorSwatchPicker.vue @@ -0,0 +1,37 @@ + + + From a547d277bca189bd6f9aacec478abeb843ef7a45 Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:39:00 +1000 Subject: [PATCH 7/9] feat: change color picker colors --- .../demo/ColorSwatchPicker/tailwind/index.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/components/demo/ColorSwatchPicker/tailwind/index.vue b/docs/components/demo/ColorSwatchPicker/tailwind/index.vue index 04e26a4d25..cccced8106 100644 --- a/docs/components/demo/ColorSwatchPicker/tailwind/index.vue +++ b/docs/components/demo/ColorSwatchPicker/tailwind/index.vue @@ -3,8 +3,8 @@ import { Icon } from '@iconify/vue' import { blue, green, + indigo, orange, - pink, red, violet, yellow, @@ -13,12 +13,12 @@ import { ColorSwatchPickerItem, ColorSwatchPickerItemIndicator, ColorSwatchPicke const colors = [ red.red9, - pink.pink9, - violet.violet9, - blue.blue9, - green.green9, orange.orange9, yellow.yellow9, + green.green9, + blue.blue9, + indigo.indigo9, + violet.violet9, ] From b4c081facc135f68d99ed81ec46122dfcbb9d907 Mon Sep 17 00:00:00 2001 From: Mark Janiczak Date: Mon, 14 Jul 2025 13:41:56 +1000 Subject: [PATCH 8/9] feat: docs --- docs/content/docs/components/color-swatch-picker.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/components/color-swatch-picker.md b/docs/content/docs/components/color-swatch-picker.md index 9bcce6b227..092b1da05f 100644 --- a/docs/content/docs/components/color-swatch-picker.md +++ b/docs/content/docs/components/color-swatch-picker.md @@ -48,15 +48,15 @@ import {