mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-14 18:05:55 +08:00
[ui] make disabled color more consistent
This commit is contained in:
@ -6,6 +6,7 @@ import { trpc } from '~/utils/trpc';
|
||||
|
||||
type Props = Readonly<{
|
||||
disabled?: boolean;
|
||||
errorMessage?: string;
|
||||
isLabelHidden?: boolean;
|
||||
onSelect: (option: TypeaheadOption) => void;
|
||||
placeHolder?: string;
|
||||
|
@ -74,11 +74,18 @@ export function HiddenLabel() {
|
||||
|
||||
export function Disabled() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<TextArea
|
||||
disabled={true}
|
||||
label="Comment"
|
||||
placeholder="You can't type here, it's disabled."
|
||||
placeholder="You can't type here, it's disabled. (Placeholder)"
|
||||
/>
|
||||
<TextArea
|
||||
disabled={true}
|
||||
label="Comment"
|
||||
value="You can't type here, it's disabled. (Value)"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -111,9 +111,15 @@ export function Disabled() {
|
||||
<TextInput
|
||||
disabled={true}
|
||||
label="Disabled input"
|
||||
placeholder="John Doe"
|
||||
placeholder="John Doe (Placeholder)"
|
||||
type="text"
|
||||
/>
|
||||
<TextInput
|
||||
disabled={true}
|
||||
label="Disabled input"
|
||||
type="text"
|
||||
value="John Doe (Value)"
|
||||
/>
|
||||
<TextInput
|
||||
disabled={true}
|
||||
endAddOn={
|
||||
|
@ -128,3 +128,57 @@ export function Required() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Disabled() {
|
||||
return (
|
||||
<Typeahead
|
||||
disabled={true}
|
||||
label="Author"
|
||||
options={[]}
|
||||
placeholder="John Doe"
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onQueryChange={() => {}}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Error() {
|
||||
const people = [
|
||||
{ id: '1', label: 'Wade Cooper', value: '1' },
|
||||
{ id: '2', label: 'Arlene Mccoy', value: '2' },
|
||||
{ id: '3', label: 'Devon Webb', value: '3' },
|
||||
{ id: '4', label: 'Tom Cook', value: '4' },
|
||||
{ id: '5', label: 'Tanya Fox', value: '5' },
|
||||
{ id: '6', label: 'Hellen Schmidt', value: '6' },
|
||||
];
|
||||
const [selectedEntry, setSelectedEntry] = useState<TypeaheadOption>(
|
||||
people[0],
|
||||
);
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const filteredPeople =
|
||||
query === ''
|
||||
? people
|
||||
: people.filter((person) =>
|
||||
person.label
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '')
|
||||
.includes(query.toLowerCase().replace(/\s+/g, '')),
|
||||
);
|
||||
|
||||
return (
|
||||
<Typeahead
|
||||
errorMessage={
|
||||
selectedEntry.id === '1' ? 'Cannot select Wade Cooper' : undefined
|
||||
}
|
||||
label="Author"
|
||||
options={filteredPeople}
|
||||
required={true}
|
||||
value={selectedEntry}
|
||||
onQueryChange={setQuery}
|
||||
onSelect={setSelectedEntry}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ function CheckboxInput(
|
||||
className={clsx(
|
||||
'h-4 w-4 rounded border-slate-300',
|
||||
disabled
|
||||
? 'bg-slate-100 text-slate-400'
|
||||
? 'bg-slate-50 text-slate-400'
|
||||
: 'text-primary-600 focus:ring-primary-500',
|
||||
)}
|
||||
defaultChecked={defaultValue}
|
||||
|
@ -88,10 +88,9 @@ function Select<T>(
|
||||
aria-label={isLabelHidden ? label : undefined}
|
||||
className={clsx(
|
||||
display === 'block' && 'block w-full',
|
||||
'rounded-md py-2 pl-3 pr-8 text-sm focus:outline-none',
|
||||
'rounded-md py-2 pl-3 pr-8 text-sm focus:outline-none disabled:bg-slate-50 disabled:text-slate-500',
|
||||
stateClasses[state],
|
||||
borderClasses[borderStyle],
|
||||
disabled && 'bg-slate-100',
|
||||
)}
|
||||
defaultValue={defaultValue != null ? String(defaultValue) : undefined}
|
||||
disabled={disabled}
|
||||
|
@ -108,9 +108,8 @@ function TextArea(
|
||||
aria-describedby={hasError ? errorId : undefined}
|
||||
aria-invalid={hasError ? true : undefined}
|
||||
className={clsx(
|
||||
'block w-full rounded-md text-sm',
|
||||
'block w-full rounded-md text-sm disabled:bg-slate-50 disabled:text-slate-500',
|
||||
stateClasses[state].textArea,
|
||||
disabled && 'bg-slate-100',
|
||||
resizeClasses[resize],
|
||||
)}
|
||||
defaultValue={defaultValue}
|
||||
|
@ -143,7 +143,7 @@ function TextInput(
|
||||
<div
|
||||
className={clsx(
|
||||
'flex w-full overflow-hidden rounded-md border text-sm focus-within:ring-1',
|
||||
disabled && 'pointer-events-none select-none bg-slate-100',
|
||||
disabled && 'pointer-events-none select-none bg-slate-50',
|
||||
containerClass,
|
||||
)}>
|
||||
{(() => {
|
||||
@ -178,9 +178,8 @@ function TextInput(
|
||||
aria-describedby={hasError ? errorId : undefined}
|
||||
aria-invalid={hasError ? true : undefined}
|
||||
className={clsx(
|
||||
'w-0 flex-1 border-none text-sm focus:outline-none focus:ring-0',
|
||||
'w-0 flex-1 border-none text-sm focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:bg-transparent disabled:text-slate-500',
|
||||
inputClass,
|
||||
disabled && 'bg-transparent',
|
||||
)}
|
||||
defaultValue={defaultValue}
|
||||
disabled={disabled}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import type { InputHTMLAttributes } from 'react';
|
||||
import { useId } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { Combobox, Transition } from '@headlessui/react';
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid';
|
||||
@ -24,6 +25,7 @@ type Attributes = Pick<
|
||||
>;
|
||||
|
||||
type Props = Readonly<{
|
||||
errorMessage?: React.ReactNode;
|
||||
isLabelHidden?: boolean;
|
||||
label: string;
|
||||
noResultsMessage?: string;
|
||||
@ -39,6 +41,27 @@ type Props = Readonly<{
|
||||
}> &
|
||||
Readonly<Attributes>;
|
||||
|
||||
type State = 'error' | 'normal';
|
||||
|
||||
const stateClasses: Record<
|
||||
State,
|
||||
Readonly<{
|
||||
container: string;
|
||||
input: string;
|
||||
}>
|
||||
> = {
|
||||
error: {
|
||||
container:
|
||||
'border-danger-300 focus-within:outline-none focus-within:ring-danger-500 focus-within:border-danger-500',
|
||||
input: 'text-danger-900 placeholder-danger-300',
|
||||
},
|
||||
normal: {
|
||||
container:
|
||||
'focus-within:ring-primary-500 focus-within:border-primary-500 border-slate-300',
|
||||
input: 'placeholder:text-slate-400',
|
||||
},
|
||||
};
|
||||
|
||||
const textSizes: Record<TypeaheadTextSize, string> = {
|
||||
default: 'text-sm',
|
||||
inherit: '',
|
||||
@ -46,6 +69,7 @@ const textSizes: Record<TypeaheadTextSize, string> = {
|
||||
|
||||
export default function Typeahead({
|
||||
disabled = false,
|
||||
errorMessage,
|
||||
isLabelHidden,
|
||||
label,
|
||||
noResultsMessage = 'No results',
|
||||
@ -58,7 +82,11 @@ export default function Typeahead({
|
||||
onSelect,
|
||||
...props
|
||||
}: Props) {
|
||||
const hasError = errorMessage != null;
|
||||
const errorId = useId();
|
||||
const state: State = hasError ? 'error' : 'normal';
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Combobox
|
||||
@ -102,14 +130,18 @@ export default function Typeahead({
|
||||
<div className="relative">
|
||||
<div
|
||||
className={clsx(
|
||||
'focus-visible:ring-offset-primary-300 relative w-full cursor-default overflow-hidden rounded-lg border border-slate-300 bg-white text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2',
|
||||
'relative w-full cursor-default overflow-hidden rounded-md border text-left focus-within:ring-1',
|
||||
disabled && 'pointer-events-none select-none bg-slate-50',
|
||||
stateClasses[state].container,
|
||||
textSizes[textSize],
|
||||
)}>
|
||||
<Combobox.Input
|
||||
aria-describedby={hasError ? errorId : undefined}
|
||||
className={clsx(
|
||||
'w-full border-none py-2 pl-3 pr-10 leading-5 text-slate-900 focus:ring-0',
|
||||
'w-full border-none py-2 pl-3 pr-10 leading-5 focus:ring-0',
|
||||
stateClasses[state].input,
|
||||
textSizes[textSize],
|
||||
disabled && 'pointer-events-none select-none bg-slate-100',
|
||||
'disabled:cursor-not-allowed disabled:bg-transparent disabled:text-slate-500',
|
||||
)}
|
||||
displayValue={(option) =>
|
||||
(option as unknown as TypeaheadOption)?.label
|
||||
@ -170,6 +202,11 @@ export default function Typeahead({
|
||||
</Transition>
|
||||
</div>
|
||||
</Combobox>
|
||||
{errorMessage && (
|
||||
<p className="text-danger-600 mt-2 text-sm" id={errorId}>
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user