Skip to content

Commit

Permalink
Add color components to tailwind starter
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Sep 30, 2024
1 parent caa2726 commit a79adcf
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 2 deletions.
2 changes: 0 additions & 2 deletions starters/tailwind/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
src
stories
storybook-static
23 changes: 23 additions & 0 deletions starters/tailwind/src/ColorArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import {
ColorArea as AriaColorArea,
ColorAreaProps as AriaColorAreaProps
} from 'react-aria-components';
import { composeTailwindRenderProps } from './utils';
import { ColorThumb } from './ColorThumb';

export interface ColorAreaProps extends AriaColorAreaProps {}

export function ColorArea(props: ColorAreaProps) {
return (
<AriaColorArea
{...props}
className={composeTailwindRenderProps(props.className, 'w-56 h-56 rounded-lg bg-gray-300 dark:bg-zinc-800 forced-colors:bg-[GrayText]')}
style={({ defaultStyle, isDisabled }) => ({
...defaultStyle,
background: isDisabled ? undefined : defaultStyle.background
})}>
<ColorThumb />
</AriaColorArea>
);
}
37 changes: 37 additions & 0 deletions starters/tailwind/src/ColorField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import {
ColorField as AriaColorField,
ColorFieldProps as AriaColorFieldProps,
ValidationResult
} from 'react-aria-components';
import { tv } from 'tailwind-variants';
import { Description, FieldError, Input, Label, fieldBorderStyles } from './Field';
import { composeTailwindRenderProps, focusRing } from './utils';

const inputStyles = tv({
extend: focusRing,
base: 'border-2 rounded-md',
variants: {
isFocused: fieldBorderStyles.variants.isFocusWithin,
...fieldBorderStyles.variants,
}
});

export interface ColorFieldProps extends AriaColorFieldProps {
label?: string;
description?: string;
errorMessage?: string | ((validation: ValidationResult) => string);
}

export function ColorField(
{ label, description, errorMessage, ...props }: ColorFieldProps
) {
return (
<AriaColorField {...props} className={composeTailwindRenderProps(props.className, 'flex flex-col gap-1')}>
{label && <Label>{label}</Label>}
<Input className={inputStyles} />
{description && <Description>{description}</Description>}
<FieldError>{errorMessage}</FieldError>
</AriaColorField>
);
}
48 changes: 48 additions & 0 deletions starters/tailwind/src/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import {Button, ColorPicker as AriaColorPicker, ColorPickerProps as AriaColorPickerProps, DialogTrigger} from 'react-aria-components';
import {ColorSwatch} from './ColorSwatch';
import {ColorArea} from './ColorArea';
import {ColorSlider} from './ColorSlider';
import {ColorField} from './ColorField';
import {Dialog} from './Dialog';
import {Popover} from './Popover';
import { tv } from 'tailwind-variants';
import { focusRing } from './utils';

const buttonStyles = tv({
extend: focusRing,
base: 'flex gap-2 items-center cursor-default rounded text-sm text-gray-800 dark:text-gray-200'
});

export interface ColorPickerProps extends AriaColorPickerProps {
label?: string;
children?: React.ReactNode;
}

export function ColorPicker({ label, children, ...props }: ColorPickerProps) {
return (
<AriaColorPicker {...props}>
<DialogTrigger>
<Button className={buttonStyles}>
<ColorSwatch />
<span>{label}</span>
</Button>
<Popover placement="bottom start">
<Dialog className="flex flex-col gap-2">
{children || (
<>
<ColorArea
colorSpace="hsb"
xChannel="saturation"
yChannel="brightness"
/>
<ColorSlider colorSpace="hsb" channel="hue" />
<ColorField label="Hex" />
</>
)}
</Dialog>
</Popover>
</DialogTrigger>
</AriaColorPicker>
);
}
46 changes: 46 additions & 0 deletions starters/tailwind/src/ColorSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import {
ColorSlider as AriaColorSlider,
ColorSliderProps as AriaColorSliderProps,
SliderOutput,
SliderTrack
} from 'react-aria-components';
import { tv } from 'tailwind-variants';
import { Label } from './Field';
import { composeTailwindRenderProps } from './utils';
import { ColorThumb } from './ColorThumb';

const trackStyles = tv({
base: 'group col-span-2 orientation-horizontal:h-6 rounded-lg',
variants: {
orientation: {
horizontal: 'w-full h-6',
vertical: 'w-6 h-56 ml-[50%] -translate-x-[50%]'
},
isDisabled: {
true: 'bg-gray-300 dark:bg-zinc-800 forced-colors:bg-[GrayText]'
}
}
});

interface ColorSliderProps extends AriaColorSliderProps {
label?: string;
}

export function ColorSlider({ label, ...props }: ColorSliderProps) {
return (
<AriaColorSlider {...props} className={composeTailwindRenderProps(props.className, 'orientation-horizontal:grid orientation-vertical:flex grid-cols-[1fr_auto] flex-col items-center gap-2 orientation-horizontal:w-56')}>
<Label>{label}</Label>
<SliderOutput className="text-sm text-gray-500 dark:text-zinc-400 font-medium orientation-vertical:hidden" />
<SliderTrack
className={trackStyles}
style={({ defaultStyle, isDisabled }) => ({
...defaultStyle,
background: isDisabled ? undefined : `${defaultStyle.background}, repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px`
})}
>
<ColorThumb />
</SliderTrack>
</AriaColorSlider>
);
}
15 changes: 15 additions & 0 deletions starters/tailwind/src/ColorSwatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import {ColorSwatch as AriaColorSwatch, ColorSwatchProps} from 'react-aria-components';
import { composeTailwindRenderProps } from './utils';

export function ColorSwatch(props: ColorSwatchProps) {
return (
<AriaColorSwatch
{...props}
className={composeTailwindRenderProps(props.className, 'w-8 h-8 rounded border border-black/10')}
style={({color}) => ({
background: `linear-gradient(${color}, ${color}),
repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px`
})} />
);
}
36 changes: 36 additions & 0 deletions starters/tailwind/src/ColorSwatchPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import {
ColorSwatchPicker as AriaColorSwatchPicker,
ColorSwatchPickerItem as AriaColorSwatchPickerItem,
ColorSwatchPickerItemProps,
ColorSwatchPickerProps
} from 'react-aria-components';
import {ColorSwatch} from './ColorSwatch';
import {composeTailwindRenderProps, focusRing} from './utils';
import {tv} from 'tailwind-variants';

export function ColorSwatchPicker(
{ children, ...props }: Omit<ColorSwatchPickerProps, 'layout'>
) {
return (
<AriaColorSwatchPicker {...props} className={composeTailwindRenderProps(props.className, 'flex gap-1')}>
{children}
</AriaColorSwatchPicker>
);
}

const itemStyles = tv({
extend: focusRing,
base: 'relative rounded'
});

export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps) {
return (
<AriaColorSwatchPickerItem {...props} className={itemStyles}>
{({isSelected}) => <>
<ColorSwatch />
{isSelected && <div className="absolute top-0 left-0 w-full h-full border border-2 border-black dark:border-white outline outline-2 outline-white dark:outline-black -outline-offset-4 rounded forced-color-adjust-none" />}
</>}
</AriaColorSwatchPickerItem>
);
}
31 changes: 31 additions & 0 deletions starters/tailwind/src/ColorThumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import {ColorThumb as AriaColorThumb, ColorThumbProps} from 'react-aria-components';
import { tv } from 'tailwind-variants';

const thumbStyles = tv({
base: 'w-6 h-6 top-[50%] left-[50%] rounded-full border-2 border-white',
variants: {
isFocusVisible: {
true: 'w-8 h-8'
},
isDragging: {
true: 'bg-gray-700 dark:bg-gray-300 forced-colors:bg-[ButtonBorder]'
},
isDisabled: {
true: 'border-gray-300 dark:border-zinc-700 forced-colors:border-[GrayText] bg-gray-300 dark:bg-zinc-800 forced-colors:bg-[GrayText]'
}
}
});

export function ColorThumb(props: ColorThumbProps) {
return (
<AriaColorThumb
{...props}
style={({ defaultStyle, isDisabled }) => ({
...defaultStyle,
backgroundColor: isDisabled ? undefined : defaultStyle.backgroundColor,
boxShadow: '0 0 0 1px black, inset 0 0 0 1px black'}
)}
className={thumbStyles} />
);
}
19 changes: 19 additions & 0 deletions starters/tailwind/src/ColorWheel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import {ColorWheel as AriaColorWheel, ColorWheelProps as AriaColorWheelProps, ColorWheelTrack} from 'react-aria-components';
import { ColorThumb } from './ColorThumb';

export interface ColorWheelProps extends Omit<AriaColorWheelProps, 'outerRadius' | 'innerRadius'> {}

export function ColorWheel(props: ColorWheelProps) {
return (
<AriaColorWheel {...props} outerRadius={100} innerRadius={74}>
<ColorWheelTrack
className="disabled:bg-gray-300 disabled:dark:bg-zinc-800 disabled:forced-colors:bg-[GrayText]"
style={({ defaultStyle, isDisabled }) => ({
...defaultStyle,
background: isDisabled ? undefined : `${defaultStyle.background}, repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px`
})} />
<ColorThumb />
</AriaColorWheel>
);
}
16 changes: 16 additions & 0 deletions starters/tailwind/stories/ColorArea.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { ColorArea } from '../src/ColorArea';

const meta: Meta<typeof ColorArea> = {
component: ColorArea,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
};

export default meta;

export const Example = (args: any) => <ColorArea {...args} />;

19 changes: 19 additions & 0 deletions starters/tailwind/stories/ColorField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { ColorField } from '../src/ColorField';

const meta: Meta<typeof ColorField> = {
component: ColorField,
parameters: {
layout: 'centered'
},
tags: ['autodocs'],
args: {
label: 'Color',
defaultValue: '#ff0'
}
};

export default meta;

export const Example = (args: any) => <ColorField {...args} />;
19 changes: 19 additions & 0 deletions starters/tailwind/stories/ColorPicker.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { ColorPicker } from '../src/ColorPicker';

const meta: Meta<typeof ColorPicker> = {
component: ColorPicker,
parameters: {
layout: 'centered'
},
tags: ['autodocs'],
args: {
label: 'Color',
defaultValue: '#ff0'
}
};

export default meta;

export const Example = (args: any) => <ColorPicker {...args} />;
22 changes: 22 additions & 0 deletions starters/tailwind/stories/ColorSlider.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { ColorSlider } from '../src/ColorSlider';

const meta: Meta<typeof ColorSlider> = {
component: ColorSlider,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
};

export default meta;

export const Example = (args: any) => <ColorSlider {...args} />;

Example.args = {
label: 'Fill Color',
channel: 'hue',
colorSpace: 'hsl',
defaultValue: '#f00'
};
19 changes: 19 additions & 0 deletions starters/tailwind/stories/ColorSwatch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { ColorSwatch } from '../src/ColorSwatch';

const meta: Meta<typeof ColorSwatch> = {
component: ColorSwatch,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
};

export default meta;

export const Example = (args: any) => <ColorSwatch {...args} />;

Example.args = {
color: '#f00a'
};
24 changes: 24 additions & 0 deletions starters/tailwind/stories/ColorSwatchPicker.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import { ColorSwatchPicker, ColorSwatchPickerItem } from '../src/ColorSwatchPicker';

const meta: Meta<typeof ColorSwatchPicker> = {
component: ColorSwatchPicker,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
};

export default meta;

export const Example = (args: any) => (
<ColorSwatchPicker {...args}>
<ColorSwatchPickerItem color="#A00" />
<ColorSwatchPickerItem color="#f80" />
<ColorSwatchPickerItem color="#080" />
<ColorSwatchPickerItem color="#08f" />
<ColorSwatchPickerItem color="#088" />
<ColorSwatchPickerItem color="#008" />
</ColorSwatchPicker>
);
Loading

0 comments on commit a79adcf

Please sign in to comment.