Skip to content

Commit

Permalink
feat(react): add color picker (#766)
Browse files Browse the repository at this point in the history
  • Loading branch information
cschroeter committed Apr 21, 2023
1 parent 8a5256a commit cd463a6
Show file tree
Hide file tree
Showing 27 changed files with 577 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/moody-grapes-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ark-ui/react': minor
---

Add color picker
64 changes: 34 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,43 @@
<img alt="GitHub stars" src="https://img.shields.io/github/stars/chakra-ui/ark?logo=github&style=for-the-badge">
</p>

## Preview components
## Available Components

| | React | Solid | Vue |
| ------------------- | ----- | ----- | --- |
| Accordion | 🟢 | 🟢 | 🟢 |
| Carousel (Beta) | 🟢 |||
| Checkbox | 🟢 | 🟢 | 🟢 |
| Color Picker (Beta) | 🟢 |||
| Date Picker (Beta) | 🟡 |||
| Dialog | 🟢 | 🟢 | 🟢 |
| Combobox | 🟢 | 🟢 | 🟢 |
| Editable | 🟢 | 🟢 | 🟢 |
| Environment | 🟢 | 🟢 | 🟡 |
| Hover Card | 🟢 | 🟢 | 🟢 |
| Menu | 🟢 | 🟢 | 🟢 |
| Number Input | 🟢 | 🟢 | 🟢 |
| Pagination | 🟢 | 🟢 | 🟢 |
| Pin Input | 🟢 | 🟢 | 🟢 |
| Popover | 🟢 | 🟢 | 🟢 |
| Pressable | 🟢 | 🟢 | 🟢 |
| Radio Group | 🟢 | 🟢 | 🟢 |
| Range Slider | 🟢 | 🟢 | 🟢 |
| Rating | 🟢 | 🟢 | 🟢 |
| Select | 🟢 | 🟢 | 🟢 |
| Slider | 🟢 | 🟢 | 🟢 |
| Splitter | 🟢 | 🟢 | 🟢 |
| Tabs | 🟢 | 🟢 | 🟢 |
| Tags Input | 🟢 | 🟢 | 🟡 |
| Toast | 🟢 | 🟢 | 🟡 |
| Tooltip | 🟢 | 🟢 | 🟢 |

## Contributing

### Preview Components

Although Ark is a headless component library, as a developer you still want to make sure that the components behave correctly.
At the time of writing neither [Storybook](https://storybook.js.org/docs/react/api/frameworks-feature-support) nor [Storybook alternatives](https://histoire.dev/) support all major frontend frameworks.

So instead we are recommending [Preview.js](https://previewjs.com/), an IDE plugin with support for React, SolidJS, Svelte and Vue.
The plugin is available for [VSCode](https://marketplace.visualstudio.com/items?itemName=zenclabs.previewjs) and [JetBrains based IDEs](https://plugins.jetbrains.com/plugin/17569-react-preview--deprecated-in-favor-of-preview-js/).

## State

| | React | Solid | Vue |
| --------------- | ----- | ----- | --- |
| Accordion | 🟢 | 🟢 | 🟢 |
| Carousel (Beta) | 🟢 |||
| Checkbox | 🟢 | 🟢 | 🟢 |
| Dialog | 🟢 | 🟢 | 🟢 |
| Combobox | 🟢 | 🟢 | 🟢 |
| Editable | 🟢 | 🟢 | 🟢 |
| Environment | 🟢 | 🟢 ||
| Hover Card | 🟢 | 🟢 | 🟢 |
| Menu | 🟢 | 🟢 | 🟢 |
| Number Input | 🟢 | 🟢 | 🟢 |
| Pagination | 🟢 | 🟢 | 🟢 |
| Pin Input | 🟢 | 🟢 | 🟢 |
| Popover | 🟢 | 🟢 | 🟢 |
| Pressable | 🟢 | 🟢 | 🟢 |
| Radio Group | 🟢 | 🟢 | 🟢 |
| Range Slider | 🟢 | 🟢 | 🟢 |
| Rating | 🟢 | 🟢 | 🟢 |
| Select | 🟢 | 🟢 | 🟢 |
| Slider | 🟢 | 🟢 | 🟢 |
| Splitter | 🟢 | 🟢 | 🟢 |
| Tabs | 🟢 | 🟢 | 🟢 |
| Tags Input | 🟢 | 🟢 ||
| Toast | 🟢 | 🟢 ||
| Tooltip | 🟢 | 🟢 | 🟢 |
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@zag-js/accordion": "0.6.0",
"@zag-js/anatomy": "0.1.4",
"@zag-js/carousel": "0.5.0",
"@zag-js/color-picker": "0.6.0",
"@zag-js/checkbox": "0.6.0",
"@zag-js/combobox": "0.6.0",
"@zag-js/dialog": "0.6.0",
Expand Down
8 changes: 8 additions & 0 deletions packages/react/src/color-picker/color-picker-area-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { AreaProps } from '@zag-js/color-picker/dist/color-picker.types'
import { createContext } from '../create-context'

export const [ColorPickerAreaProvider, useColorPickerAreaContext] = createContext<AreaProps>({
name: 'ColorPickerAreaContext',
hookName: 'useColorPickerAreaContext',
providerName: '<ColorPickerAreaProvider />',
})
17 changes: 17 additions & 0 deletions packages/react/src/color-picker/color-picker-area-gradient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { forwardRef } from '@polymorphic-factory/react'
import { mergeProps } from '@zag-js/react'
import { ark, type HTMLArkProps } from '../factory'
import { useColorPickerAreaContext } from './color-picker-area-context'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerAreaGradientProps = HTMLArkProps<'div'>

export const ColorPickerAreaGradient = forwardRef<'div', ColorPickerAreaGradientProps>(
(props, ref) => {
const areaContext = useColorPickerAreaContext()
const { getAreaGradientProps } = useColorPickerContext()
const mergedProps = mergeProps(getAreaGradientProps(areaContext), props)

return <ark.div {...mergedProps} ref={ref} />
},
)
15 changes: 15 additions & 0 deletions packages/react/src/color-picker/color-picker-area-thumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { forwardRef } from '@polymorphic-factory/react'
import { mergeProps } from '@zag-js/react'
import { ark, type HTMLArkProps } from '../factory'
import { useColorPickerAreaContext } from './color-picker-area-context'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerAreaThumbProps = HTMLArkProps<'div'>

export const ColorPickerAreaThumb = forwardRef<'div', ColorPickerAreaThumbProps>((props, ref) => {
const areaContext = useColorPickerAreaContext()
const { getAreaThumbProps } = useColorPickerContext()
const mergedProps = mergeProps(getAreaThumbProps(areaContext), props)

return <ark.div {...mergedProps} ref={ref} />
})
21 changes: 21 additions & 0 deletions packages/react/src/color-picker/color-picker-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { forwardRef } from '@polymorphic-factory/react'
import type { AreaProps } from '@zag-js/color-picker/dist/color-picker.types'
import { mergeProps } from '@zag-js/react'
import { createSplitProps } from '../create-split-props'
import { ark, type HTMLArkProps } from '../factory'
import { ColorPickerAreaProvider } from './color-picker-area-context'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerAreaProps = HTMLArkProps<'div'> & AreaProps

export const ColorPickerArea = forwardRef<'div', ColorPickerAreaProps>((props, ref) => {
const [channelProps, divprops] = createSplitProps<AreaProps>()(props, ['xChannel', 'yChannel'])
const { getAreaProps } = useColorPickerContext()
const mergedProps = mergeProps(getAreaProps(channelProps), divprops)

return (
<ColorPickerAreaProvider value={channelProps}>
<ark.div {...mergedProps} ref={ref} />
</ColorPickerAreaProvider>
)
})
21 changes: 21 additions & 0 deletions packages/react/src/color-picker/color-picker-channel-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { forwardRef } from '@polymorphic-factory/react'
import type { ChannelInputProps } from '@zag-js/color-picker/dist/color-picker.types'
import { mergeProps } from '@zag-js/react'
import { createSplitProps } from '../create-split-props'
import { ark, type HTMLArkProps } from '../factory'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerChannelInputProps = HTMLArkProps<'input'> & ChannelInputProps

export const ColorPickerChannelInput = forwardRef<'input', ColorPickerChannelInputProps>(
(props, ref) => {
const [channelProps, inputProps] = createSplitProps<ChannelInputProps>()(props, [
'channel',
'orientation',
])
const { getChannelInputProps } = useColorPickerContext()
const mergedProps = mergeProps(getChannelInputProps(channelProps), inputProps)

return <ark.input {...mergedProps} ref={ref} />
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ChannelProps } from '@zag-js/color-picker/dist/color-picker.types'
import { createContext } from '../create-context'

export const [ColorPickerSliderProvider, useColorPickerSliderContext] = createContext<ChannelProps>(
{
name: 'ColorPickerSliderContext',
hookName: 'useColorPickerSliderContext',
providerName: '<ColorPickerSliderProvider />',
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { forwardRef } from '@polymorphic-factory/react'
import { mergeProps } from '@zag-js/react'
import { ark, type HTMLArkProps } from '../factory'
import { useColorPickerSliderContext } from './color-picker-channel-slider-context'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerSliderThumbProps = HTMLArkProps<'div'>

export const ColorPickerSliderThumb = forwardRef<'div', ColorPickerSliderThumbProps>(
(props, ref) => {
const sliderContext = useColorPickerSliderContext()
const { getChannelSliderThumbProps } = useColorPickerContext()
const mergedProps = mergeProps(getChannelSliderThumbProps(sliderContext), props)

return <ark.div {...mergedProps} ref={ref} />
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { forwardRef } from '@polymorphic-factory/react'
import type { ChannelProps } from '@zag-js/color-picker/dist/color-picker.types'
import { mergeProps } from '@zag-js/react'
import { createSplitProps } from '../create-split-props'
import { ark, type HTMLArkProps } from '../factory'
import { ColorPickerSliderProvider } from './color-picker-channel-slider-context'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerSliderTrackProps = HTMLArkProps<'div'> & ChannelProps

export const ColorPickerSliderTrack = forwardRef<'div', ColorPickerSliderTrackProps>(
(props, ref) => {
const [channelProps, { children, ...divProps }] = createSplitProps<ChannelProps>()(props, [
'channel',
'orientation',
])
const { getChannelSliderTrackProps, getChannelSliderBackgroundProps } = useColorPickerContext()
const mergedProps = mergeProps(getChannelSliderTrackProps(channelProps), divProps)

return (
<ColorPickerSliderProvider value={channelProps}>
<ark.div {...mergedProps} ref={ref}>
<ark.div {...getChannelSliderBackgroundProps(channelProps)} />
{children}
</ark.div>
</ColorPickerSliderProvider>
)
},
)
13 changes: 13 additions & 0 deletions packages/react/src/color-picker/color-picker-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { forwardRef } from '@polymorphic-factory/react'
import { mergeProps } from '@zag-js/react'
import { ark, type HTMLArkProps } from '../factory'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerContentProps = HTMLArkProps<'div'>

export const ColorPickerContent = forwardRef<'div', ColorPickerContentProps>((props, ref) => {
const { contentProps } = useColorPickerContext()
const mergedProps = mergeProps(contentProps, props)

return <ark.div {...mergedProps} ref={ref} />
})
10 changes: 10 additions & 0 deletions packages/react/src/color-picker/color-picker-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createContext } from '../create-context'
import { type UseColorPickerReturn } from './use-color-picker'

export type ColorPickerContext = UseColorPickerReturn

export const [ColorPickerProvider, useColorPickerContext] = createContext<ColorPickerContext>({
name: 'ColorPickerContext',
hookName: 'useColorPickerContext',
providerName: '<ColorPickerProvider />',
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Children, cloneElement, type ReactElement } from 'react'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerEyeDropperTriggerProps = { children: ReactElement }

export const ColorPickerEyeDropperTrigger = (props: ColorPickerEyeDropperTriggerProps) => {
const { eyeDropperTriggerProps } = useColorPickerContext()

const onlyChild = Children.only(props.children)
return cloneElement(onlyChild, eyeDropperTriggerProps)
}
9 changes: 9 additions & 0 deletions packages/react/src/color-picker/color-picker-swatch-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { forwardRef } from '@polymorphic-factory/react'
import { ark, type HTMLArkProps } from '../factory'
import { parts } from './color-picker.anatomy'

export type ColorPickerSwatchGroupProps = HTMLArkProps<'div'>

export const ColorPickerSwatchGroup = forwardRef<'div', ColorPickerSwatchGroupProps>(
(props, ref) => <ark.div {...parts.swatchGroup.attrs} {...props} ref={ref} />,
)
20 changes: 20 additions & 0 deletions packages/react/src/color-picker/color-picker-swatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { forwardRef } from '@polymorphic-factory/react'
import type { SwatchProps } from '@zag-js/color-picker/dist/color-picker.types'
import { mergeProps } from '@zag-js/react'
import { createSplitProps } from '../create-split-props'
import { ark, type HTMLArkProps } from '../factory'
import { useColorPickerContext } from './color-picker-context'

export type ColorPickerSwatchProps = HTMLArkProps<'button'> & SwatchProps

export const ColorPickerSwatch = forwardRef<'button', ColorPickerSwatchProps>((props, ref) => {
const [swatchProps, buttonProps] = createSplitProps<SwatchProps>()(props, ['readOnly', 'value'])
const { getSwatchProps, getSwatchBackgroundProps } = useColorPickerContext()
const mergedProps = mergeProps(getSwatchProps(swatchProps), buttonProps)

return (
<ark.button {...mergedProps} ref={ref} disabled={swatchProps.readOnly}>
<ark.div {...getSwatchBackgroundProps(swatchProps)} />
</ark.button>
)
})
21 changes: 21 additions & 0 deletions packages/react/src/color-picker/color-picker.anatomy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createAnatomy } from '@zag-js/anatomy'
// TODO replace with anatomy from @zag-js/color-picker
// import { anatomy } from '@zag-js/color-picker'

const anatomy = createAnatomy('color-picker', [
'area',
'areaThumb',
'areaGradient',
'channelSliderTrack',
'channelSliderTrackBg',
'channelSliderThumb',
'channelInput',
'swatch',
'swatchBg',
'content',
'label',
'eyeDropTrigger',
])

export const colorPickerAnatomy = anatomy.extendWith('swatchGroup')
export const parts = colorPickerAnatomy.build()
69 changes: 69 additions & 0 deletions packages/react/src/color-picker/color-picker.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[data-scope='color-picker'][data-part='content'] {
max-width: 260px;
display: flex;
flex-direction: column;
box-sizing: border-box;
gap: 16px;
padding: 24px;
border: 1px solid #d5d5d5;
}

[data-scope='color-picker'][data-part='area'] {
height: 200px;
border-radius: 4px;
border: 1px solid #ebebeb;
}

[data-scope='color-picker'][data-part='area-gradient'] {
background: rgb(142, 142, 142);
border-radius: 4px;
height: 200px;
}

[data-scope='color-picker'][data-part='area-thumb'],
[data-scope='color-picker'][data-part='channel-slider-thumb'] {
border: 2px solid white;
border-radius: 9999px;
box-sizing: border-box;
transform: translate(-50%, -50%);
box-shadow: black 0px 0px 0px 1px, black 0px 0px 0px 1px inset;
width: 16px;
height: 16px;
}

[data-scope='color-picker'][data-part='channel-slider-track'] {
height: 20px;
border-radius: 4px;
}

[data-scope='color-picker'][data-part='channel-slider-track-bg'] {
border-radius: 4px;
}

[data-scope='color-picker'][data-part='channel-input'] {
border-radius: 4px;
width: 100%;
border: 1px solid #c2c2c2;
}

[data-scope='color-picker'][data-part='channel-input']::-webkit-outer-spin-button,
[data-scope='color-picker'][data-part='channel-input']::-webkit-inner-spin-button {
-webkit-appearance: none;
}

[data-scope='color-picker'][data-part='swatch-group'] {
display: flex;
gap: 4px;
}

[data-scope='color-picker'][data-part='swatch'] {
all: unset;
border-radius: 4px;
width: 20px;
height: 20px;
flex-shrink: 0;
}

[data-scope='color-picker'][data-part='swatch-bg'] {
border-radius: 4px;
}
Loading

1 comment on commit cd463a6

@vercel
Copy link

@vercel vercel bot commented on cd463a6 Apr 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.