Browse Source

Implement color picker component

Style native input type color component.
master
TheoryOfNekomata 1 year ago
parent
commit
79b54d91df
13 changed files with 241 additions and 23 deletions
  1. +2
    -2
      TODO.md
  2. +10
    -4
      categories/color/react/package.json
  3. +19
    -0
      categories/color/react/scripts/build.ts
  4. +7
    -0
      categories/color/react/src/components/ColorPicker/ColorPicker.css
  5. +75
    -0
      categories/color/react/src/components/ColorPicker/index.tsx
  6. +12
    -6
      categories/color/react/src/components/Swatch/index.tsx
  7. +1
    -0
      categories/color/react/src/index.ts
  8. +56
    -0
      pnpm-lock.yaml
  9. +8
    -8
      showcases/web-kitchensink-reactnext/src/components/temporal/WeekInput/index.tsx
  10. +1
    -1
      showcases/web-kitchensink-reactnext/src/components/temporal/index.ts
  11. +2
    -0
      showcases/web-kitchensink-reactnext/src/pages/_app.tsx
  12. +46
    -0
      showcases/web-kitchensink-reactnext/src/pages/categories/color/index.tsx
  13. +2
    -2
      showcases/web-kitchensink-reactnext/src/pages/categories/temporal/index.tsx

+ 2
- 2
TODO.md View File

@@ -3,7 +3,7 @@
- Action
- [X] ActionButton
- Blob (choose to extract each preview component to their own library?)
- [ ] FileSelectBox
- [-] FileSelectBox
- Choice
- [X] ComboBox
- [X] DropdownSelect
@@ -61,7 +61,7 @@
- [ ] MonthDayInput
- [X] TimeSpinner
- [-] YearMonthInput
- [-] YearWeekInput
- [-] WeekInput
- [ ] YearInput

# Others


+ 10
- 4
categories/color/react/package.json View File

@@ -15,6 +15,8 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/color": "^3.0.3",
"@types/color-convert": "^2.0.0",
"@types/node": "^18.14.1",
"@types/react": "^18.0.27",
"eslint": "^8.35.0",
@@ -25,6 +27,7 @@
"react-dom": "^18.2.0",
"react-test-renderer": "^18.2.0",
"tslib": "^2.5.0",
"tsx": "^3.12.7",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
},
@@ -34,7 +37,7 @@
},
"scripts": {
"prepublishOnly": "pridepack clean && pridepack build",
"build": "pridepack build",
"build": "pridepack build && tsx scripts/build.ts",
"type-check": "pridepack check",
"lint": "pridepack lint",
"clean": "pridepack clean",
@@ -58,7 +61,9 @@
"access": "public"
},
"dependencies": {
"clsx": "^1.2.1"
"clsx": "^1.2.1",
"color": "^4.2.3",
"color-convert": "^2.0.1"
},
"types": "./dist/types/index.d.ts",
"main": "./dist/cjs/production/index.js",
@@ -72,9 +77,10 @@
"require": "./dist/cjs/production/index.js",
"import": "./dist/esm/production/index.js",
"types": "./dist/types/index.d.ts"
}
},
"./dist/ColorPicker.css": "./dist/ColorPicker.css"
},
"typesVersions": {
"*": {}
}
}
}

+ 19
- 0
categories/color/react/scripts/build.ts View File

@@ -0,0 +1,19 @@
import { copyFileSync, readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';

const doCopy = (src: string, dest: string) => {
const trueSrc = resolve(src);
const trueDest = resolve(dest);
console.log('Copying...');
console.log(`${trueSrc} -> ${trueDest}`);
copyFileSync(trueSrc, trueDest);
const packageJsonContents = readFileSync('./package.json', 'utf-8');
const packageJson = JSON.parse(packageJsonContents);
packageJson.exports[dest] = dest;
const newPackageJsonContents = JSON.stringify(packageJson, null, 2);
console.log('Updating package.json...');
writeFileSync('./package.json', newPackageJsonContents);
console.log('Done');
}

doCopy('./src/components/ColorPicker/ColorPicker.css', './dist/ColorPicker.css');

+ 7
- 0
categories/color/react/src/components/ColorPicker/ColorPicker.css View File

@@ -0,0 +1,7 @@
.color-picker::-webkit-color-swatch-wrapper {
padding: 0;
}

.color-picker::-webkit-color-swatch {
border: 2px solid black;
}

+ 75
- 0
categories/color/react/src/components/ColorPicker/index.tsx View File

@@ -0,0 +1,75 @@
import * as React from 'react';
import clsx from 'clsx';

export type ColorPickerDerivedElement = HTMLInputElement;

export interface ColorPickerProps extends Omit<React.HTMLProps<ColorPickerDerivedElement>, 'size' | 'type' | 'label'> {
square?: boolean;
size?: 'small' | 'medium' | 'large';
}

export const ColorPicker = React.forwardRef<
ColorPickerDerivedElement,
ColorPickerProps
>((
{
className,
id: idProp,
style,
square = false,
size = 'medium' as const,
...etcProps
},
forwardedRef,
) => {
return (
<div
className={clsx(
'inline-block align-center relative',
{
'w-4': square && size === 'small',
'w-6': square && size === 'medium',
'w-8': square && size === 'large',
},
{
'w-8': !square && size === 'small',
'w-12': !square && size === 'medium',
'w-16': !square && size === 'large',
},
className,
)}
style={style}
>
<span
className={clsx(
'block w-full',
{
'p-[50%]': square,
'p-[25%]': !square,
},
)}
>
<input
{...etcProps}
className={clsx(
'color-picker absolute top-0 left-0 w-full h-full overflow-hidden ring-secondary/50 rounded cursor-pointer',
'border-2 border-primary focus:border-secondary active:border-tertiary disabled:border-primary',
'focus:outline-0 focus:ring-4',
'active:ring-tertiary/50',
'disabled:opacity-50 disabled:cursor-not-allowed',
)}
ref={forwardedRef}
id={idProp}
type="color"
/>
</span>
</div>
);
});

ColorPicker.displayName = 'ColorPicker';

ColorPicker.defaultProps = {
square: false as const,
size: 'medium' as const,
};

+ 12
- 6
categories/color/react/src/components/Swatch/index.tsx View File

@@ -1,13 +1,17 @@
import * as React from 'react';
import clsx from 'clsx';
type RgbTuple = [number, number, number];
import Color from 'color';
import * as convert from 'color-convert';

export type SwatchDerivedElement = HTMLInputElement;

type ColorValue = ConstructorParameters<typeof Color>;

type ColorMode = keyof typeof convert;

export interface SwatchProps extends Omit<React.HTMLProps<SwatchDerivedElement>, 'color'> {
color: RgbTuple;
mode?: 'rgb' | 'hsl';
color: ColorValue;
mode?: ColorMode;
}

export const useSwatchControls = () => {
@@ -32,7 +36,9 @@ export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({
...etcProps
}, forwardedRef) => {
const { id, copyColor } = useSwatchControls();
const colorValue = `${mode}(${color.join(', ')})`;
const colorInternal = React.useMemo(() => new Color(color, mode), [color, mode]);

const colorValue = colorInternal.toString();

return (
<span
@@ -68,7 +74,7 @@ export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({
<span
className="block w-full h-full border border-[#000000]"
style={{
backgroundColor: `${mode}(${color.join(' ')})`,
backgroundColor: colorInternal.hex(),
}}
/>
</span>


+ 1
- 0
categories/color/react/src/index.ts View File

@@ -1 +1,2 @@
export * from './components/ColorPicker';
export * from './components/Swatch';

+ 56
- 0
pnpm-lock.yaml View File

@@ -212,6 +212,12 @@ importers:
clsx:
specifier: ^1.2.1
version: 1.2.1
color:
specifier: ^4.2.3
version: 4.2.3
color-convert:
specifier: ^2.0.1
version: 2.0.1
devDependencies:
'@testing-library/jest-dom':
specifier: ^5.16.5
@@ -219,6 +225,12 @@ importers:
'@testing-library/react':
specifier: ^13.4.0
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
'@types/color':
specifier: ^3.0.3
version: 3.0.3
'@types/color-convert':
specifier: ^2.0.0
version: 2.0.0
'@types/node':
specifier: ^18.14.1
version: 18.14.1
@@ -249,6 +261,9 @@ importers:
tslib:
specifier: ^2.5.0
version: 2.6.0
tsx:
specifier: ^3.12.7
version: 3.12.7
typescript:
specifier: ^4.9.5
version: 4.9.5
@@ -2039,6 +2054,22 @@ packages:
resolution: {integrity: sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw==}
dev: true

/@types/color-convert@2.0.0:
resolution: {integrity: sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==}
dependencies:
'@types/color-name': 1.1.1
dev: true

/@types/color-name@1.1.1:
resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==}
dev: true

/@types/color@3.0.3:
resolution: {integrity: sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==}
dependencies:
'@types/color-convert': 2.0.0
dev: true

/@types/get-image-colors@4.0.2:
resolution: {integrity: sha512-8E/xA3Dyl70sboWbjjt+UEHTC2Nvv6EIDxPx5nCSTN+QfBWbx60gGyBH0pQ9ABrRNqQ2gKtGboK3MoZodxMWtw==}
dependencies:
@@ -2938,6 +2969,21 @@ packages:
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}

/color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
dev: false

/color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
dev: false

/colorthief@2.4.0:
resolution: {integrity: sha512-0U48RGNRo5fVO+yusBwgp+d3augWSorXabnqXUu9SabEhCpCgZJEUjUTTI41OOBBYuMMxawa3177POT6qLfLeQ==}
dependencies:
@@ -4529,6 +4575,10 @@ packages:
get-intrinsic: 1.2.1
is-typed-array: 1.1.10

/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
dev: false

/is-bigint@1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
dependencies:
@@ -6113,6 +6163,12 @@ packages:
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}

/simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
dependencies:
is-arrayish: 0.3.2
dev: false

/sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
dev: true


showcases/web-kitchensink-reactnext/src/components/temporal/YearWeekInput/index.tsx → showcases/web-kitchensink-reactnext/src/components/temporal/WeekInput/index.tsx View File

@@ -2,9 +2,9 @@ import * as React from 'react';
import { TextControl } from '@tesseract-design/web-base';
import clsx from 'clsx';

export type YearWeekInputDerivedElement = HTMLInputElement;
export type WeekInputDerivedElement = HTMLInputElement;

export interface YearWeekInputProps extends Omit<React.HTMLProps<YearWeekInputDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
export interface WeekInputProps extends Omit<React.HTMLProps<WeekInputDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
/**
* Short textual description indicating the nature of the component's value.
*/
@@ -42,9 +42,9 @@ export interface YearWeekInputProps extends Omit<React.HTMLProps<YearWeekInputDe
/**
* Component for inputting textual values.
*/
export const YearWeekInput = React.forwardRef<
YearWeekInputDerivedElement,
YearWeekInputProps
export const WeekInput = React.forwardRef<
WeekInputDerivedElement,
WeekInputProps
>((
{
label,
@@ -59,7 +59,7 @@ export const YearWeekInput = React.forwardRef<
id: idProp,
style,
...etcProps
}: YearWeekInputProps,
}: WeekInputProps,
forwardedRef,
) => {
const labelId = React.useId();
@@ -201,9 +201,9 @@ export const YearWeekInput = React.forwardRef<
);
});

YearWeekInput.displayName = 'YearWeekInput';
WeekInput.displayName = 'WeekInput';

YearWeekInput.defaultProps = {
WeekInput.defaultProps = {
label: undefined,
hint: undefined,
size: 'medium',

+ 1
- 1
showcases/web-kitchensink-reactnext/src/components/temporal/index.ts View File

@@ -1,3 +1,3 @@
export * from './TimeSpinner';
export * from './YearMonthInput';
export * from './YearWeekInput';
export * from './WeekInput';

+ 2
- 0
showcases/web-kitchensink-reactnext/src/pages/_app.tsx View File

@@ -6,6 +6,8 @@ import '@tesseract-design/web-choice-react/dist/MenuSelect.css'
import '@tesseract-design/web-choice-react/dist/RadioButton.css'
import '@tesseract-design/web-choice-react/dist/RadioTickBox.css'

import '@tesseract-design/web-color-react/dist/ColorPicker.css'

import '@tesseract-design/web-multichoice-react/dist/MenuMultiSelect.css'
import '@tesseract-design/web-multichoice-react/dist/TagInput.css'
import '@tesseract-design/web-multichoice-react/dist/ToggleButton.css'


+ 46
- 0
showcases/web-kitchensink-reactnext/src/pages/categories/color/index.tsx View File

@@ -0,0 +1,46 @@
import {NextPage} from 'next';
import {DefaultLayout} from '@/components/DefaultLayout';
import {Section, Subsection} from '@/components/Section';
import * as Color from '@tesseract-design/web-color-react';

const ColorPage: NextPage = () => {
return (
<DefaultLayout title="Color">
<Section title="DateDropdown">
<Subsection title="Default">
<Color.ColorPicker
size="small"
defaultValue="#ff0000"
/>
<Color.ColorPicker
size="medium"
defaultValue="#00ff00"
/>
<Color.ColorPicker
size="large"
defaultValue="#0000ff"
/>
</Subsection>
<Subsection title="Square">
<Color.ColorPicker
size="small"
square
defaultValue="#ff0000"
/>
<Color.ColorPicker
size="medium"
square
defaultValue="#00ff00"
/>
<Color.ColorPicker
size="large"
square
defaultValue="#0000ff"
/>
</Subsection>
</Section>
</DefaultLayout>
)
}

export default ColorPage;

+ 2
- 2
showcases/web-kitchensink-reactnext/src/pages/categories/temporal/index.tsx View File

@@ -50,9 +50,9 @@ const TemporalPage: NextPage = () => {
/>
</Subsection>
</Section>
<Section title="YearWeekInput">
<Section title="WeekInput">
<Subsection title="Default">
<TemporalWip.YearWeekInput
<TemporalWip.WeekInput
label="Vacation"
variant="default"
border


Loading…
Cancel
Save