Skip to content

Commit

Permalink
feat(circuit-ui): ColorInput experimental component
Browse files Browse the repository at this point in the history
  • Loading branch information
matoous committed Aug 27, 2024
1 parent dc091d4 commit 4252f4a
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 0 deletions.
13 changes: 13 additions & 0 deletions packages/circuit-ui/components/ColorInput/ColorInput.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Meta, Status, Props, Story } from '../../../../.storybook/components';
import * as Stories from './ColorInput.stories';

<Meta of={Stories} />

# ColorInput

<Status variant="stable" />

The ColorInput component enables users to submit or select a color.

<Story of={Stories.Base} />
<Props />
42 changes: 42 additions & 0 deletions packages/circuit-ui/components/ColorInput/ColorInput.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.suffix {
overflow: hidden;
border-top-right-radius: var(--cui-border-radius-byte);
border-bottom-right-radius: var(--cui-border-radius-byte);
pointer-events: auto !important;
border: none;
border-left: 1px solid var(--cui-border-normal);
width: var(--cui-spacings-exa);
height: var(--cui-spacings-exa);
position: absolute;
top: 0;
right: 0;
box-shadow: none;
}

.prefix {
display: flex;
align-items: center;
justify-content: center;
line-height: var(--cui-spacings-mega);
}

.colorInput {
opacity: 0;
width: var(--cui-spacings-exa);
height: var(--cui-spacings-exa);
border: none;
box-shadow: none;
padding: 0;
}

.input {
font-family: var(--cui-font-stack-mono);
}

.colorpick {
display: inline-block;
}

.colorpick:focus-within input {
box-shadow: 0 0 0 2px var(--cui-border-accent);
}
39 changes: 39 additions & 0 deletions packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright 2019, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { describe, expect, it } from 'vitest';
import { createRef } from 'react';

import { render, axe } from '../../util/test-utils.js';
import type { InputElement } from '../Input/index.js';

import { ColorInput } from './ColorInput.js';

describe('SearchInput', () => {
const baseProps = { label: 'Car color', pickerLabel: 'Pick car color' };

it('should forward a ref', () => {
const ref = createRef<InputElement>();
const { container } = render(<ColorInput {...baseProps} ref={ref} />);
const input = container.querySelector("input[type='color']");
expect(ref.current).toBe(input);
});

it('should have no accessibility violations', async () => {
const { container } = render(<ColorInput {...baseProps} />);
const actual = await axe(container);
expect(actual).toHaveNoViolations();
});
});
33 changes: 33 additions & 0 deletions packages/circuit-ui/components/ColorInput/ColorInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2019, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ColorInput, type ColorInputProps } from './ColorInput.js';

export default {
title: 'Forms/Input/ColorInput',
component: ColorInput,
};

const baseArgs = {
label: 'Color',
pickerLabel: 'Pick color',
placeholder: 'abc321',
};

export const Base = (args: ColorInputProps) => (
<ColorInput {...args} style={{ maxWidth: '250px' }} />
);

Base.args = baseArgs;
142 changes: 142 additions & 0 deletions packages/circuit-ui/components/ColorInput/ColorInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Copyright 2024, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use client';

import {
forwardRef,
useCallback,
useEffect,
useId,
useRef,
useState,
type ChangeEventHandler,
} from 'react';

import { Input, type InputElement, type InputProps } from '../Input/index.js';
import { clsx } from '../../styles/clsx.js';
import { FieldLabel, FieldLabelText } from '../Field/index.js';
import { applyMultipleRefs } from '../../util/refs.js';

import styles from './ColorInput.module.css';

export interface ColorInputProps
extends Omit<
InputProps,
| 'ref'
| 'type'
| 'defaultValue'
| 'value'
| 'placeholder'
| 'maxLength'
| 'pattern'
| 'renderPrefix'
| 'renderSuffix'
| 'as'
> {
/**
* A short string that is shown inside the empty input.
*/
placeholder?: string;
/**
* The value of the input element.
*/
value?: string;
/**
* The default value of the input element.
*/
defaultValue?: string;
/*
* Picker label describes the color picker which serves as an alternative
* to the hex color input.
*/
pickerLabel: string;
}

export const ColorInput = forwardRef<InputElement, ColorInputProps>(
(
{ onChange, className, value, defaultValue, pickerLabel, ...props },
ref,
) => {
const [currentColor, setCurrentColor] = useState<string | undefined>(
defaultValue,
);
const colorDisplayRef = useRef<HTMLDivElement>(null);
const colorPickerRef = useRef<InputElement>(null);
const pickerId = useId();

const onPickerColorChange: ChangeEventHandler<InputElement> = (e) => {
setCurrentColor(e.target.value);
if (onChange) {
onChange(e);
}
};

const onInputChange: ChangeEventHandler<InputElement> = (e) => {
if (colorPickerRef.current) {
colorPickerRef.current.value = `#${e.target.value}`;
}
setCurrentColor(`#${e.target.value}`);
};

useEffect(() => {
if (colorDisplayRef.current && currentColor) {
colorDisplayRef.current.style.backgroundColor = currentColor;
}
}, [currentColor]);

const renderSuffix = useCallback(
() => (
<div
className={clsx(styles.suffix)}
ref={colorDisplayRef}
style={{ backgroundColor: currentColor }}
>
<FieldLabel htmlFor={pickerId}>
<FieldLabelText label={pickerLabel} hideLabel />
</FieldLabel>
<input
type="color"
id={pickerId}
className={styles.colorInput}
onChange={onPickerColorChange}
ref={applyMultipleRefs(colorPickerRef, ref)}
/>
</div>
),
[],
);

return (
<Input
className={styles.colorpick}
renderPrefix={({ className }) => (

Check failure on line 125 in packages/circuit-ui/components/ColorInput/ColorInput.tsx

View workflow job for this annotation

GitHub Actions / ci (18)

'className' is already declared in the upper scope on line 70 column 17

Check failure on line 125 in packages/circuit-ui/components/ColorInput/ColorInput.tsx

View workflow job for this annotation

GitHub Actions / ci (20)

'className' is already declared in the upper scope on line 70 column 17
<div className={clsx(className, styles.prefix)}>
<span>#</span>
</div>
)}
renderSuffix={renderSuffix}
value={currentColor ? currentColor.replace('#', '') : undefined}
inputClassName={styles.input}
maxLength={6}
pattern="[0-9a-f]{3,6}"
onChange={onInputChange}
{...props}
/>
);
},
);

ColorInput.displayName = 'ColorInput';
18 changes: 18 additions & 0 deletions packages/circuit-ui/components/ColorInput/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2019, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { ColorInput } from './ColorInput.js';

export type { ColorInputProps } from './ColorInput.js';

0 comments on commit 4252f4a

Please sign in to comment.