Render all option components.pull/1/head
@@ -2,7 +2,7 @@ import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||
type ActionButtonDerivedElement = HTMLButtonElement; | |||
export type ActionButtonDerivedElement = HTMLButtonElement; | |||
export interface ActionButtonProps extends Omit<React.HTMLProps<ActionButtonDerivedElement>, 'type' | 'size'> { | |||
type?: ButtonBase.ButtonType; | |||
@@ -90,11 +90,16 @@ export const ActionButton = React.forwardRef<ActionButtonDerivedElement, ActionB | |||
)} | |||
</span> | |||
{badge && ( | |||
<span | |||
data-testid="badge" | |||
> | |||
{badge} | |||
</span> | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
data-testid="badge" | |||
> | |||
{badge} | |||
</span> | |||
</> | |||
)} | |||
{menuItem && ( | |||
<span | |||
@@ -0,0 +1,9 @@ | |||
import * as React from 'react'; | |||
export interface CommonPreviewProps<F extends Partial<File> = Partial<File>> { | |||
file?: F; | |||
disabled?: boolean; | |||
enhanced?: boolean; | |||
} | |||
export type FilePreviewComponent<T extends CommonPreviewProps = CommonPreviewProps> = (props: T) => React.ReactNode; |
@@ -15,14 +15,12 @@ import {SpectrogramCanvas, WaveformCanvas} from '@modal-soft/react-wavesurfer'; | |||
import {Slider} from '@/categories/number/react'; | |||
import {KeyValueTable} from '@/categories/information/react'; | |||
import {useEnhanced} from '@modal-soft/react-utils'; | |||
import {CommonPreviewProps} from '@/categories/blob/react/common'; | |||
type AudioFilePreviewDerivedElement = HTMLAudioElement; | |||
export type AudioFilePreviewDerivedElement = HTMLAudioElement; | |||
export interface AudioFilePreviewProps<F extends Partial<File> = Partial<File>> extends Omit<React.HTMLProps<AudioFilePreviewDerivedElement>, 'controls'> { | |||
file?: F; | |||
disabled?: boolean; | |||
enhanced?: boolean; | |||
} | |||
export interface AudioFilePreviewProps<F extends Partial<File> = Partial<File>> | |||
extends Omit<React.HTMLProps<AudioFilePreviewDerivedElement>, 'controls'>, CommonPreviewProps<F> {} | |||
export const AudioFilePreview = React.forwardRef<AudioFilePreviewDerivedElement, AudioFilePreviewProps>(({ | |||
file, | |||
@@ -6,13 +6,12 @@ import clsx from 'clsx'; | |||
import {KeyValueTable} from '@/categories/information/react'; | |||
import {BinaryDataCanvas} from '@modal-soft/react-binary-data-canvas'; | |||
import {useEnhanced} from '@modal-soft/react-utils'; | |||
import {CommonPreviewProps} from '@/categories/blob/react/common'; | |||
type BinaryFilePreviewDerivedElement = HTMLDivElement; | |||
export type BinaryFilePreviewDerivedElement = HTMLDivElement; | |||
export interface BinaryFilePreviewProps<F extends Partial<File> = Partial<File>> extends React.HTMLProps<BinaryFilePreviewDerivedElement> { | |||
file?: F; | |||
enhanced?: boolean; | |||
} | |||
export interface BinaryFilePreviewProps<F extends Partial<File> = Partial<File>> | |||
extends React.HTMLProps<BinaryFilePreviewDerivedElement>, CommonPreviewProps<F> {} | |||
export const BinaryFilePreview = React.forwardRef<BinaryFilePreviewDerivedElement, BinaryFilePreviewProps>(({ | |||
file, | |||
@@ -1,13 +1,28 @@ | |||
import * as React from 'react'; | |||
import {ContentType, FileWithResolvedContentType, getMimeTypeDescription} from '@/utils/blob'; | |||
import { FilePreview } from '../FilePreview'; | |||
import {ContentType, FileWithResolvedContentType, getContentType, getMimeTypeDescription} from '@/utils/blob'; | |||
import {formatFileSize} from '@/utils/numeral'; | |||
import {AudioMiniFilePreview} from '@tesseract-design/web-blob-react'; | |||
import {AudioFilePreview} from '../AudioFilePreview'; | |||
import {BinaryFilePreview} from '../BinaryFilePreview'; | |||
import {ImageFilePreview} from '../ImageFilePreview'; | |||
import {VideoFilePreview} from '../VideoFilePreview'; | |||
import {TextFilePreview} from '../TextFilePreview'; | |||
import {AudioMiniFilePreview} from '../AudioMiniFilePreview'; | |||
import {delegateTriggerChangeEvent} from '@/utils/event'; | |||
import clsx from 'clsx'; | |||
import {useEnhanced} from '@modal-soft/react-utils'; | |||
import {FilePreviewComponent} from '@/categories/blob/react/common'; | |||
export interface FileButtonProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style' | 'label' | 'list'> { | |||
const FILE_PREVIEW_COMPONENTS: Record<ContentType, FilePreviewComponent> = { | |||
[ContentType.IMAGE]: ImageFilePreview, | |||
[ContentType.AUDIO]: AudioFilePreview, | |||
[ContentType.VIDEO]: VideoFilePreview, | |||
[ContentType.BINARY]: BinaryFilePreview, | |||
[ContentType.TEXT]: TextFilePreview, | |||
}; | |||
export type FileSelectBoxDerivedElement = HTMLInputElement; | |||
export interface FileSelectBoxProps extends Omit<React.HTMLProps<FileSelectBoxDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list'> { | |||
/** | |||
* Should the component display a border? | |||
*/ | |||
@@ -31,52 +46,7 @@ export interface FileButtonProps extends Omit<React.HTMLProps<HTMLInputElement>, | |||
hiddenLabel?: boolean, | |||
} | |||
const FilePreviewGrid = ({ | |||
fileList: files, | |||
}: { fileList?: FileList }) => { | |||
if (!files) { | |||
return null; | |||
} | |||
return ( | |||
<div className="w-full h-full overflow-auto -mx-4 px-4"> | |||
<div className="w-full grid gap-4 grid-cols-3"> | |||
{Array.from(files).map((file: File, i) => { | |||
const f = file as unknown as FileWithResolvedContentType; | |||
return ( | |||
<div | |||
data-testid="selectedFileItem" | |||
key={i} | |||
className={`w-full aspect-square rounded overflow-hidden relative before:absolute before:content-[''] before:bg-current before:top-0 before:left-0 before:w-full before:h-full before:opacity-10`} | |||
title={[f.name, getMimeTypeDescription(f.type), formatFileSize(f.size)].join(', ')} | |||
> | |||
{ | |||
f.resolvedType === ContentType.IMAGE | |||
&& typeof f?.url === 'string' | |||
&& ( | |||
<img | |||
className="block w-full h-full object-center object-cover" | |||
src={f.url} | |||
alt={f.name} | |||
data-testid="preview" | |||
/> | |||
) | |||
} | |||
{ | |||
f.resolvedType === ContentType.AUDIO | |||
&& ( | |||
<AudioMiniFilePreview file={f} /> | |||
) | |||
} | |||
</div> | |||
); | |||
})} | |||
</div> | |||
</div> | |||
) | |||
} | |||
export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>( | |||
export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileSelectBoxProps>( | |||
( | |||
{ | |||
label = '', | |||
@@ -91,7 +61,7 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps> | |||
className, | |||
id: idProp, | |||
...etcProps | |||
}: FileButtonProps, | |||
}: FileSelectBoxProps, | |||
forwardedRef, | |||
) => { | |||
const { enhanced } = useEnhanced({ enhanced: enhancedProp }); | |||
@@ -155,7 +125,7 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps> | |||
}); | |||
} | |||
const filesCount = React.useMemo(() => fileList?.length ?? 0, [fileList]); | |||
const filesCount = fileList?.length ?? 0; | |||
return ( | |||
<div | |||
@@ -174,7 +144,6 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps> | |||
data-testid="clickArea" | |||
htmlFor={id} | |||
/> | |||
<input | |||
{...etcProps} | |||
id={id} | |||
@@ -237,23 +206,68 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps> | |||
{ | |||
multiple | |||
&& ( | |||
<FilePreviewGrid fileList={fileList} /> | |||
<div className="w-full h-full overflow-auto -mx-4 px-4"> | |||
<div className="w-full grid gap-4 grid-cols-3"> | |||
{Array.from(fileList ?? []).map((file: File) => { | |||
const f = file as unknown as FileWithResolvedContentType; | |||
const fileContentType = getContentType(file.type, file.name); | |||
return ( | |||
<div | |||
data-testid="selectedFileItem" | |||
key={f?.url ?? f?.name} | |||
className={`w-full aspect-square rounded overflow-hidden relative before:absolute before:content-[''] before:bg-current before:top-0 before:left-0 before:w-full before:h-full before:opacity-10`} | |||
title={[f.name, getMimeTypeDescription(f.type), formatFileSize(f.size)].join(', ')} | |||
> | |||
{ | |||
fileContentType === ContentType.IMAGE | |||
&& typeof f?.url === 'string' | |||
&& ( | |||
<img | |||
className="block w-full h-full object-center object-cover" | |||
src={f.url} | |||
alt={f.name} | |||
data-testid="preview" | |||
/> | |||
) | |||
} | |||
{ | |||
fileContentType === ContentType.AUDIO | |||
&& ( | |||
<AudioMiniFilePreview file={f} /> | |||
) | |||
} | |||
</div> | |||
); | |||
})} | |||
</div> | |||
</div> | |||
) | |||
} | |||
{ | |||
!multiple | |||
&& ( | |||
<div | |||
className={`w-full h-full`} | |||
> | |||
<div data-testid="selectedFileItem" className={`h-full w-full p-4 box-border rounded overflow-hidden relative before:absolute before:content-[''] before:bg-current before:top-0 before:left-0 before:w-full before:h-full before:opacity-10`}> | |||
<FilePreview | |||
className="w-full h-full relative" | |||
fileList={fileList} | |||
/> | |||
&& Array.from(fileList ?? []).map((file) => { | |||
const f = file as unknown as FileWithResolvedContentType; | |||
const fileContentType = getContentType(file.type, file.name); | |||
const { [fileContentType]: FilePreviewComponent = BinaryFilePreview } = FILE_PREVIEW_COMPONENTS; | |||
return ( | |||
<div | |||
key={f?.url ?? f?.name} | |||
className="w-full h-full" | |||
> | |||
<div | |||
data-testid="selectedFileItem" | |||
className="h-full w-full p-4 box-border rounded overflow-hidden relative before:absolute before:content-[''] before:bg-current before:top-0 before:left-0 before:w-full before:h-full before:opacity-10" | |||
> | |||
<FilePreviewComponent | |||
className="w-full h-full relative" | |||
file={f} | |||
enhanced={enhanced} | |||
disabled={disabled} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
) | |||
) | |||
}) | |||
} | |||
</div> | |||
</div> | |||
@@ -5,111 +5,13 @@ import clsx from 'clsx'; | |||
import {useFileMetadata, useFileUrl, useImageControls} from '@/categories/blob/react'; | |||
import {KeyValueTable} from '@/categories/information/react'; | |||
import {useEnhanced} from '@modal-soft/react-utils'; | |||
import {CommonPreviewProps} from '@/categories/blob/react/common'; | |||
import {Swatch} from '@tesseract-design/web-color-react'; | |||
type RgbTuple = [number, number, number]; | |||
export type ImageFilePreviewDerivedElement = HTMLImageElement; | |||
type SwatchDerivedElement = HTMLInputElement; | |||
export interface SwatchProps extends Omit<React.HTMLProps<SwatchDerivedElement>, 'color'> { | |||
color: RgbTuple; | |||
mode?: 'rgb' | 'hsl'; | |||
} | |||
export const useSwatchControls = () => { | |||
const id = React.useId(); | |||
const copyColor: React.ReactEventHandler<SwatchDerivedElement> = React.useCallback(async (e) => { | |||
const { value } = e.currentTarget; | |||
await window.navigator.clipboard.writeText(value); | |||
}, []); | |||
return React.useMemo(() => ({ | |||
id, | |||
copyColor, | |||
}), [id, copyColor]); | |||
}; | |||
export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({ | |||
color, | |||
mode = 'rgb', | |||
className, | |||
style, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const { id, copyColor } = useSwatchControls(); | |||
const colorValue = `${mode}(${color.join(', ')})`; | |||
return ( | |||
<span | |||
className={clsx( | |||
'inline-block align-middle', | |||
className, | |||
)} | |||
style={style} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={forwardedRef} | |||
type="text" | |||
value={colorValue} | |||
className="sr-only select-all peer" | |||
readOnly | |||
id={id} | |||
onSelect={copyColor} | |||
/> | |||
<label | |||
className={clsx( | |||
'relative rounded ring-secondary/50 whitespace-nowrap inline-block align-top leading-none cursor-pointer', // todo eyedropper cursor | |||
'peer-focus:outline-0 peer-focus:ring-4', | |||
'peer-active:ring-tertiary/50', | |||
'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed', | |||
)} | |||
title={colorValue} | |||
htmlFor={id} | |||
> | |||
<span | |||
className="inline-block w-5 h-5 align-middle border border-[#ffffff]" | |||
> | |||
<span | |||
className="block w-full h-full border border-[#000000]" | |||
style={{ | |||
backgroundColor: `${mode}(${color.join(' ')})`, | |||
}} | |||
/> | |||
</span> | |||
<span className="tabular-nums text-xs sr-only"> | |||
{ | |||
color | |||
.map((c) => c | |||
.toString() | |||
.padStart(4, ' ') | |||
.split('') | |||
.map((cc, j) => ( | |||
<span | |||
key={`${cc}:${j}`} | |||
className={clsx({ | |||
'opacity-0': cc === ' ', | |||
})} | |||
> | |||
{j === 0 && ' '} | |||
{cc === ' ' && j > 0 ? '0' : cc} | |||
</span> | |||
)) | |||
) | |||
} | |||
</span> | |||
</label> | |||
</span> | |||
); | |||
}); | |||
Swatch.displayName = 'Swatch'; | |||
type ImageFilePreviewDerivedElement = HTMLImageElement; | |||
export interface ImageFilePreviewProps<F extends Partial<File> = Partial<File>> extends Omit<React.HTMLProps<ImageFilePreviewDerivedElement>, 'src' | 'alt'> { | |||
file?: F; | |||
disabled?: boolean; | |||
enhanced?: boolean; | |||
} | |||
export interface ImageFilePreviewProps<F extends Partial<File> = Partial<File>> | |||
extends Omit<React.HTMLProps<ImageFilePreviewDerivedElement>, 'src' | 'alt'>, CommonPreviewProps<F> {} | |||
export const ImageFilePreview = React.forwardRef<ImageFilePreviewDerivedElement, ImageFilePreviewProps>(({ | |||
file, | |||
@@ -6,13 +6,12 @@ import clsx from 'clsx'; | |||
import {KeyValueTable} from '@/categories/information/react'; | |||
import {Refractor} from '@modal-soft/react-refractor'; | |||
import {useEnhanced} from '@modal-soft/react-utils'; | |||
import {CommonPreviewProps} from '@/categories/blob/react/common'; | |||
type TextFilePreviewDerivedComponent = HTMLDivElement; | |||
export interface TextFilePreviewProps<F extends Partial<File> = Partial<File>> extends React.HTMLProps<TextFilePreviewDerivedComponent> { | |||
file?: F; | |||
enhanced?: boolean; | |||
} | |||
export interface TextFilePreviewProps<F extends Partial<File> = Partial<File>> | |||
extends React.HTMLProps<TextFilePreviewDerivedComponent>, CommonPreviewProps<F> {} | |||
export const TextFilePreview = React.forwardRef<TextFilePreviewDerivedComponent, TextFilePreviewProps>(({ | |||
file, | |||
@@ -6,14 +6,12 @@ import clsx from 'clsx'; | |||
import {Slider} from '@tesseract-design/web-number-react'; | |||
import {KeyValueTable} from '@/categories/information/react'; | |||
import {useEnhanced} from '@modal-soft/react-utils'; | |||
import {CommonPreviewProps} from '@/categories/blob/react/common'; | |||
type VideoFilePreviewDerivedComponent = HTMLVideoElement; | |||
export type VideoFilePreviewDerivedComponent = HTMLVideoElement; | |||
export interface VideoFilePreviewProps<F extends Partial<File> = Partial<File>> extends Omit<React.HTMLProps<VideoFilePreviewDerivedComponent>, 'controls'> { | |||
file?: F; | |||
disabled?: boolean; | |||
enhanced?: boolean; | |||
} | |||
export interface VideoFilePreviewProps<F extends Partial<File> = Partial<File>> | |||
extends Omit<React.HTMLProps<VideoFilePreviewDerivedComponent>, 'controls'>, CommonPreviewProps<F> {} | |||
export const VideoFilePreview = React.forwardRef<VideoFilePreviewDerivedComponent, VideoFilePreviewProps>(({ | |||
file, | |||
@@ -0,0 +1,99 @@ | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
type RgbTuple = [number, number, number]; | |||
export type SwatchDerivedElement = HTMLInputElement; | |||
export interface SwatchProps extends Omit<React.HTMLProps<SwatchDerivedElement>, 'color'> { | |||
color: RgbTuple; | |||
mode?: 'rgb' | 'hsl'; | |||
} | |||
export const useSwatchControls = () => { | |||
const id = React.useId(); | |||
const copyColor: React.ReactEventHandler<SwatchDerivedElement> = React.useCallback(async (e) => { | |||
const { value } = e.currentTarget; | |||
await window.navigator.clipboard.writeText(value); | |||
}, []); | |||
return React.useMemo(() => ({ | |||
id, | |||
copyColor, | |||
}), [id, copyColor]); | |||
}; | |||
export const Swatch = React.forwardRef<SwatchDerivedElement, SwatchProps>(({ | |||
color, | |||
mode = 'rgb', | |||
className, | |||
style, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const { id, copyColor } = useSwatchControls(); | |||
const colorValue = `${mode}(${color.join(', ')})`; | |||
return ( | |||
<span | |||
className={clsx( | |||
'inline-block align-middle', | |||
className, | |||
)} | |||
style={style} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={forwardedRef} | |||
type="text" | |||
value={colorValue} | |||
className="sr-only select-all peer" | |||
readOnly | |||
id={id} | |||
onSelect={copyColor} | |||
/> | |||
<label | |||
className={clsx( | |||
'relative rounded ring-secondary/50 whitespace-nowrap inline-block align-top leading-none cursor-pointer', // todo eyedropper cursor | |||
'peer-focus:outline-0 peer-focus:ring-4', | |||
'peer-active:ring-tertiary/50', | |||
'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed', | |||
)} | |||
title={colorValue} | |||
htmlFor={id} | |||
> | |||
<span | |||
className="inline-block w-5 h-5 align-middle border border-[#ffffff]" | |||
> | |||
<span | |||
className="block w-full h-full border border-[#000000]" | |||
style={{ | |||
backgroundColor: `${mode}(${color.join(' ')})`, | |||
}} | |||
/> | |||
</span> | |||
<span className="tabular-nums text-xs sr-only"> | |||
{ | |||
color | |||
.map((c) => c | |||
.toString() | |||
.padStart(4, ' ') | |||
.split('') | |||
.map((cc, j) => ( | |||
<span | |||
key={`${cc}:${j}`} | |||
className={clsx({ | |||
'opacity-0': cc === ' ', | |||
})} | |||
> | |||
{j === 0 && ' '} | |||
{cc === ' ' && j > 0 ? '0' : cc} | |||
</span> | |||
)) | |||
) | |||
} | |||
</span> | |||
</label> | |||
</span> | |||
); | |||
}); | |||
Swatch.displayName = 'Swatch'; |
@@ -0,0 +1 @@ | |||
export * from './components/Swatch'; |
@@ -74,20 +74,30 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP | |||
{children} | |||
</span> | |||
{subtext && ( | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
{badge && ( | |||
<span | |||
data-testid="badge" | |||
> | |||
{badge} | |||
</span> | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
data-testid="badge" | |||
> | |||
{badge} | |||
</span> | |||
</> | |||
)} | |||
{menuItem && ( | |||
<span | |||
@@ -61,141 +61,151 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>( | |||
variant = 'default' as const, | |||
hiddenLabel = false, | |||
className, | |||
children, | |||
...etcProps | |||
}: ComboBoxProps, | |||
ref, | |||
) => { | |||
const labelId = React.useId(); | |||
const datalistId = React.useId(); | |||
return ( | |||
<div | |||
className={clsx( | |||
'relative rounded ring-secondary/50', | |||
'focus-within:ring-4', | |||
{ | |||
'block': block, | |||
'inline-block align-middle': !block, | |||
}, | |||
className, | |||
)} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={ref} | |||
aria-labelledby={labelId} | |||
type={type} | |||
data-testid="input" | |||
<> | |||
<datalist | |||
id={datalistId} | |||
> | |||
{children} | |||
</datalist> | |||
<div | |||
className={clsx( | |||
'bg-negative rounded-inherit w-full peer block', | |||
'focus:outline-0', | |||
'disabled:opacity-50 disabled:cursor-not-allowed', | |||
{ | |||
'text-xxs': size === 'small', | |||
'text-xs': size === 'medium', | |||
}, | |||
{ | |||
'pl-4': variant === 'default', | |||
'pl-1.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-4': variant === 'alternate', | |||
}, | |||
'relative rounded ring-secondary/50', | |||
'focus-within:ring-4', | |||
{ | |||
'pr-4': variant === 'default' && !indicator, | |||
'pr-1.5': variant === 'alternate' && !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
'block': block, | |||
'inline-block align-middle': !block, | |||
}, | |||
className, | |||
)} | |||
/> | |||
{ | |||
label && ( | |||
<label | |||
data-testid="label" | |||
id={labelId} | |||
className={clsx( | |||
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative', | |||
{ | |||
'sr-only': hiddenLabel, | |||
}, | |||
{ | |||
'pr-1': !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
<span className="w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis"> | |||
{label} | |||
</span> | |||
</label> | |||
) | |||
} | |||
{hint && ( | |||
<div | |||
data-testid="hint" | |||
> | |||
<input | |||
{...etcProps} | |||
ref={ref} | |||
aria-labelledby={labelId} | |||
type={type} | |||
list={datalistId} | |||
data-testid="input" | |||
className={clsx( | |||
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative', | |||
'bg-negative rounded-inherit w-full peer block', | |||
'focus:outline-0', | |||
'disabled:opacity-50 disabled:cursor-not-allowed', | |||
{ | |||
'text-xxs': size === 'small', | |||
'text-xs': size === 'medium', | |||
}, | |||
{ | |||
'bottom-0 pl-4 pb-1': variant === 'default', | |||
'top-0.5': variant === 'alternate', | |||
'pl-4': variant === 'default', | |||
'pl-1.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-2': variant === 'alternate' && size === 'small', | |||
'pt-3': variant === 'alternate' && size !== 'small', | |||
'pt-4': variant === 'alternate', | |||
}, | |||
{ | |||
'pr-4': !indicator && variant === 'default', | |||
'pr-1': !indicator && variant === 'alternate', | |||
'pr-4': variant === 'default' && !indicator, | |||
'pr-1.5': variant === 'alternate' && !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
}, | |||
)} | |||
> | |||
/> | |||
{ | |||
label && ( | |||
<label | |||
data-testid="label" | |||
id={labelId} | |||
className={clsx( | |||
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative', | |||
{ | |||
'sr-only': hiddenLabel, | |||
}, | |||
{ | |||
'pr-1': !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
<span className="w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis"> | |||
{label} | |||
</span> | |||
</label> | |||
) | |||
} | |||
{hint && ( | |||
<div | |||
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis" | |||
data-testid="hint" | |||
className={clsx( | |||
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative', | |||
{ | |||
'bottom-0 pl-4 pb-1': variant === 'default', | |||
'top-0.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-2': variant === 'alternate' && size === 'small', | |||
'pt-3': variant === 'alternate' && size !== 'small', | |||
}, | |||
{ | |||
'pr-4': !indicator && variant === 'default', | |||
'pr-1': !indicator && variant === 'alternate', | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
{hint} | |||
<div | |||
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis" | |||
> | |||
{hint} | |||
</div> | |||
</div> | |||
</div> | |||
)} | |||
{indicator && ( | |||
<div | |||
className={clsx( | |||
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none', | |||
{ | |||
'w-10': size === 'small', | |||
'w-12': size === 'medium', | |||
'w-16': size === 'large', | |||
}, | |||
)} | |||
> | |||
{indicator} | |||
</div> | |||
)} | |||
{ | |||
border && ( | |||
<span | |||
data-testid="border" | |||
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary" | |||
/> | |||
) | |||
} | |||
</div> | |||
)} | |||
{indicator && ( | |||
<div | |||
className={clsx( | |||
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none', | |||
{ | |||
'w-10': size === 'small', | |||
'w-12': size === 'medium', | |||
'w-16': size === 'large', | |||
}, | |||
)} | |||
> | |||
{indicator} | |||
</div> | |||
)} | |||
{ | |||
border && ( | |||
<span | |||
data-testid="border" | |||
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary" | |||
/> | |||
) | |||
} | |||
</div> | |||
</> | |||
); | |||
} | |||
); | |||
@@ -1,23 +1,212 @@ | |||
import * as React from 'react'; | |||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||
import clsx from 'clsx'; | |||
type MenuSelectDerivedElement = HTMLSelectElement; | |||
export interface MenuSelectProps extends React.HTMLProps<MenuSelectDerivedElement> { | |||
export interface MenuSelectProps extends Omit<React.HTMLProps<MenuSelectDerivedElement>, 'size' | 'style' | 'label'> { | |||
/** | |||
* Short textual description indicating the nature of the component's value. | |||
*/ | |||
label?: React.ReactNode, | |||
/** | |||
* Short textual description as guidelines for valid input values. | |||
*/ | |||
hint?: React.ReactNode, | |||
/** | |||
* Size of the component. | |||
*/ | |||
size?: TextControlBase.TextControlSize, | |||
/** | |||
* Additional description, usually graphical, indicating the nature of the component's value. | |||
*/ | |||
indicator?: React.ReactNode, | |||
/** | |||
* Should the component display a border? | |||
*/ | |||
border?: boolean, | |||
/** | |||
* Should the component occupy the whole width of its parent? | |||
*/ | |||
block?: boolean, | |||
/** | |||
* Style of the component. | |||
*/ | |||
variant?: TextControlBase.TextControlVariant, | |||
/** | |||
* Is the label hidden? | |||
*/ | |||
hiddenLabel?: boolean, | |||
} | |||
export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectProps>(({ | |||
children, | |||
...etcProps | |||
}, forwardedRef) => { | |||
return ( | |||
<div> | |||
<select | |||
{...etcProps} | |||
ref={forwardedRef} | |||
/> | |||
</div> | |||
) | |||
}); | |||
/** | |||
* Component for inputting textual values. | |||
* | |||
* This component supports multiline input and adjusts its layout accordingly. | |||
*/ | |||
export const MenuSelect = React.forwardRef<MenuSelectDerivedElement, MenuSelectProps>( | |||
( | |||
{ | |||
label, | |||
hint, | |||
indicator, | |||
size = 'medium' as const, | |||
border = false, | |||
block = false, | |||
variant = 'default' as const, | |||
hiddenLabel = false, | |||
className, | |||
...etcProps | |||
}: MenuSelectProps, | |||
ref, | |||
) => { | |||
const labelId = React.useId(); | |||
return ( | |||
<div | |||
className={clsx( | |||
'relative rounded ring-secondary/50', | |||
'focus-within:ring-4', | |||
{ | |||
'block': block, | |||
'inline-block align-middle': !block, | |||
}, | |||
className, | |||
)} | |||
> | |||
<select | |||
{...etcProps} | |||
ref={ref} | |||
aria-labelledby={labelId} | |||
data-testid="input" | |||
size={2} | |||
style={{ | |||
height: 0, | |||
}} | |||
className={clsx( | |||
'bg-negative rounded-inherit w-full peer block', | |||
'focus:outline-0', | |||
'disabled:opacity-50 disabled:cursor-not-allowed', | |||
{ | |||
'resize': !block, | |||
'resize-y': block, | |||
}, | |||
{ | |||
'text-xxs': size === 'small', | |||
'text-xs': size === 'medium', | |||
}, | |||
{ | |||
'pl-4': variant === 'default', | |||
'pl-1.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-4': variant === 'alternate' && size === 'small', | |||
'pt-5': variant === 'alternate' && size === 'medium', | |||
'pt-8': variant === 'alternate' && size === 'large', | |||
}, | |||
{ | |||
'py-2.5': variant === 'default' && size === 'small', | |||
'py-3': variant === 'default' && size === 'medium', | |||
'py-5': variant === 'default' && size === 'large', | |||
}, | |||
{ | |||
'pr-4': variant === 'default' && !indicator, | |||
'pr-1.5': variant === 'alternate' && !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
{ | |||
'min-h-10': size === 'small', | |||
'min-h-12': size === 'medium', | |||
'min-h-16': size === 'large', | |||
}, | |||
)} | |||
/> | |||
{ | |||
label && ( | |||
<label | |||
data-testid="label" | |||
id={labelId} | |||
className={clsx( | |||
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative', | |||
{ | |||
'sr-only': hiddenLabel, | |||
}, | |||
{ | |||
'pr-1': !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
<span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis"> | |||
{label} | |||
</span> | |||
</label> | |||
) | |||
} | |||
{hint && ( | |||
<div | |||
data-testid="hint" | |||
className={clsx( | |||
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative', | |||
{ | |||
'bottom-0 pl-4 pb-1': variant === 'default', | |||
'top-0.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-2': variant === 'alternate' && size === 'small', | |||
'pt-3': variant === 'alternate' && size !== 'small', | |||
}, | |||
{ | |||
'pr-4': !indicator && variant === 'default', | |||
'pr-1': !indicator && variant === 'alternate', | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
<div | |||
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis" | |||
> | |||
{hint} | |||
</div> | |||
</div> | |||
)} | |||
{indicator && ( | |||
<div | |||
className={clsx( | |||
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none', | |||
{ | |||
'w-10': size === 'small', | |||
'w-12': size === 'medium', | |||
'w-16': size === 'large', | |||
}, | |||
)} | |||
> | |||
{indicator} | |||
</div> | |||
)} | |||
{ | |||
border && ( | |||
<span | |||
data-testid="border" | |||
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary" | |||
/> | |||
) | |||
} | |||
</div> | |||
); | |||
} | |||
); | |||
MenuSelect.displayName = 'MenuSelect'; |
@@ -1,3 +1,145 @@ | |||
export const RadioButton = () => ( | |||
<input type="radio" /> | |||
); | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||
import styles from './style.module.css'; | |||
export type RadioButtonDerivedElement = HTMLInputElement; | |||
export interface RadioButtonProps extends Omit<React.InputHTMLAttributes<RadioButtonDerivedElement>, 'type' | 'size'> { | |||
block?: boolean; | |||
compact?: boolean; | |||
size?: ButtonBase.ButtonSize; | |||
subtext?: React.ReactNode; | |||
badge?: React.ReactNode; | |||
variant: ButtonBase.ButtonVariant; | |||
} | |||
export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButtonProps>(({ | |||
children, | |||
block = false, | |||
compact = false, | |||
size = 'medium', | |||
id: idProp, | |||
className, | |||
subtext, | |||
badge, | |||
variant, | |||
style, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const defaultId = React.useId(); | |||
const id = idProp ?? defaultId; | |||
return ( | |||
<> | |||
<input | |||
{...etcProps} | |||
ref={forwardedRef} | |||
type="radio" | |||
id={id} | |||
className={clsx( | |||
'sr-only peer', | |||
styles['radio-button'], | |||
)} | |||
/> | |||
<label | |||
style={style} | |||
htmlFor={id} | |||
className={clsx( | |||
'items-center justify-start rounded overflow-hidden ring-secondary/50 gap-4 leading-none select-none cursor-pointer', | |||
'peer-focus:outline-0 peer-focus:ring-4 peer-focus:ring-secondary/50', | |||
'active:ring-tertiary/50 active:ring-4', | |||
'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:ring-0', | |||
'text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary', | |||
{ | |||
'flex w-full': block, | |||
'inline-flex max-w-full align-middle': !block, | |||
}, | |||
{ | |||
'pl-2 pr-2': compact, | |||
'pl-4 pr-4': !compact, | |||
}, | |||
{ | |||
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary' : variant !== 'bare', | |||
'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
}, | |||
className, | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'w-6 h-6 block rounded-full border-2 p-0.5 box-border', | |||
{ | |||
'-mr-2': compact, | |||
'border-current': variant !== 'filled', | |||
'border-negative': variant === 'filled', | |||
} | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'w-full h-full rounded-full bg-current', | |||
{ | |||
'text-current': variant !== 'filled', | |||
'text-negative': variant === 'filled', | |||
}, | |||
)} | |||
/> | |||
</span> | |||
<span | |||
className={clsx( | |||
'contents', | |||
{ | |||
'text-current': variant !== 'filled', | |||
'text-negative': variant === 'filled', | |||
}, | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'flex-auto min-w-0', | |||
)} | |||
> | |||
<span | |||
className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded" | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
{subtext && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
{badge && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
data-testid="badge" | |||
> | |||
{badge} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
</label> | |||
</> | |||
) | |||
}); | |||
RadioButton.displayName = 'RadioButton'; |
@@ -0,0 +1,7 @@ | |||
.radio-button + label > :first-child > :first-child { | |||
display: none; | |||
} | |||
.radio-button:checked + label > :first-child > :first-child { | |||
display: block; | |||
} |
@@ -1,3 +1,88 @@ | |||
export const RadioTickBox = () => ( | |||
<input type="radio" /> | |||
); | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import styles from './style.module.css'; | |||
export type RadioTickBoxDerivedElement = HTMLInputElement; | |||
export interface RadioTickBoxProps extends Omit<React.InputHTMLAttributes<RadioTickBoxDerivedElement>, 'type' | 'size'> { | |||
block?: boolean; | |||
subtext?: React.ReactNode; | |||
badge?: React.ReactNode; | |||
} | |||
export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTickBoxProps>(({ | |||
children, | |||
block = false, | |||
id: idProp, | |||
className, | |||
subtext, | |||
badge, | |||
style, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const defaultId = React.useId(); | |||
const id = idProp ?? defaultId; | |||
return ( | |||
<div | |||
className={clsx( | |||
'flex gap-x-4 flex-wrap', | |||
className, | |||
)} | |||
style={style} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={forwardedRef} | |||
type="radio" | |||
id={id} | |||
className={clsx( | |||
'sr-only peer/radio', | |||
styles['radio-tick-box'], | |||
)} | |||
/> | |||
<label | |||
htmlFor={id} | |||
className="peer/children order-2 cursor-pointer peer-disabled/radio:cursor-not-allowed" | |||
> | |||
<span | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
</label> | |||
<label | |||
htmlFor={id} | |||
className={clsx( | |||
'order-1 block rounded-full ring-secondary/50 overflow-hidden gap-4 leading-none select-none cursor-pointer', | |||
'peer-focus/radio:outline-0 peer-focus/radio:ring-4 peer-focus/radio:ring-secondary/50', | |||
'active:ring-tertiary/50 active:ring-4', | |||
'peer-active/children:ring-tertiary/50 peer-active/children:ring-4 peer-active/children:text-tertiary', | |||
'peer-disabled/radio:opacity-50 peer-disabled/radio:cursor-not-allowed peer-disabled/radio:ring-0', | |||
'text-primary peer-disabled/radio:text-primary peer-focus/radio:text-secondary peer-checked/radio:text-tertiary active:text-tertiary', | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'w-6 h-6 block rounded-full border-2 p-0.5 box-border border-current', | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'w-full h-full rounded-full bg-current text-current', | |||
)} | |||
/> | |||
</span> | |||
</label> | |||
{subtext && ( | |||
<div | |||
className="block w-full font-semi-expanded text-xs pl-10 order-3" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</div> | |||
)} | |||
</div> | |||
) | |||
}); | |||
RadioTickBox.displayName = 'RadioTickBox'; |
@@ -0,0 +1,7 @@ | |||
.radio-tick-box + label + label > :first-child > :first-child { | |||
display: none; | |||
} | |||
.radio-tick-box:checked + label + label > :first-child > :first-child { | |||
display: block; | |||
} |
@@ -1,6 +1,7 @@ | |||
import * as React from 'react'; | |||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||
import clsx from 'clsx'; | |||
import {useEnhanced} from '@/packages/react-utils'; | |||
type TagInputDerivedElement = HTMLInputElement; | |||
@@ -41,6 +42,7 @@ export interface TagInputProps extends Omit<React.HTMLProps<TagInputDerivedEleme | |||
* Is the label hidden? | |||
*/ | |||
hiddenLabel?: boolean, | |||
enhanced?: boolean, | |||
} | |||
/** | |||
@@ -61,11 +63,38 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>( | |||
variant = 'default' as const, | |||
hiddenLabel = false, | |||
className, | |||
enhanced: enhancedProp = false, | |||
...etcProps | |||
}: TagInputProps, | |||
ref, | |||
forwardedRef, | |||
) => { | |||
const {enhanced} = useEnhanced({ enhanced: enhancedProp }); | |||
const defaultRef = React.useRef<TagInputDerivedElement>(null); | |||
const ref = forwardedRef ?? defaultRef; | |||
const labelId = React.useId(); | |||
const dummyInputRef = React.useRef<HTMLInputElement>(null); | |||
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => { | |||
if (!(typeof ref === 'object' && ref)) { | |||
return; | |||
} | |||
const { current: input } = ref; | |||
if (!input) { | |||
return; | |||
} | |||
input.focus(); | |||
}; | |||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => { | |||
if (!(typeof dummyInputRef === 'object' && dummyInputRef)) { | |||
return; | |||
} | |||
const { current: input } = dummyInputRef; | |||
if (!input) { | |||
return; | |||
} | |||
input.value = e.currentTarget.value; | |||
}; | |||
return ( | |||
<div | |||
@@ -86,36 +115,49 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>( | |||
type={type} | |||
data-testid="input" | |||
className={clsx( | |||
'bg-negative rounded-inherit w-full peer block', | |||
'focus:outline-0', | |||
'disabled:opacity-50 disabled:cursor-not-allowed', | |||
{ | |||
'text-xxs': size === 'small', | |||
'text-xs': size === 'medium', | |||
}, | |||
{ | |||
'pl-4': variant === 'default', | |||
'pl-1.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-4': variant === 'alternate', | |||
}, | |||
{ | |||
'pr-4': variant === 'default' && !indicator, | |||
'pr-1.5': variant === 'alternate' && !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
}, | |||
'peer', | |||
enhanced && 'sr-only', | |||
)} | |||
onChange={handleChange} | |||
/> | |||
{enhanced && ( | |||
<input | |||
ref={dummyInputRef} | |||
readOnly | |||
tabIndex={-1} | |||
onFocus={handleFocus} | |||
className={clsx( | |||
'bg-negative rounded-inherit w-full block', | |||
'focus:outline-0', | |||
'disabled:opacity-50 disabled:cursor-not-allowed', | |||
{ | |||
'text-xxs': size === 'small', | |||
'text-xs': size === 'medium', | |||
}, | |||
{ | |||
'pl-4': variant === 'default', | |||
'pl-1.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-4': variant === 'alternate', | |||
}, | |||
{ | |||
'pr-4': variant === 'default' && !indicator, | |||
'pr-1.5': variant === 'alternate' && !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
}, | |||
)} | |||
/> | |||
)} | |||
{ | |||
label && ( | |||
<label | |||
@@ -1,3 +1,180 @@ | |||
export const ToggleButton = () => ( | |||
<input type="checkbox" /> | |||
); | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import * as ButtonBase from '@tesseract-design/web-base-button'; | |||
import styles from './style.module.css'; | |||
export type ToggleButtonDerivedElement = HTMLInputElement; | |||
export interface ToggleButtonProps extends Omit<React.InputHTMLAttributes<ToggleButtonDerivedElement>, 'type' | 'size'> { | |||
block?: boolean; | |||
compact?: boolean; | |||
size?: ButtonBase.ButtonSize; | |||
subtext?: React.ReactNode; | |||
badge?: React.ReactNode; | |||
variant: ButtonBase.ButtonVariant; | |||
indeterminate?: boolean; | |||
} | |||
export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleButtonProps>(({ | |||
children, | |||
block = false, | |||
compact = false, | |||
size = 'medium', | |||
id: idProp, | |||
className, | |||
subtext, | |||
badge, | |||
variant, | |||
indeterminate = false, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const defaultRef = React.useRef<ToggleButtonDerivedElement>(null); | |||
const ref = forwardedRef ?? defaultRef; | |||
const defaultId = React.useId(); | |||
const id = idProp ?? defaultId; | |||
React.useEffect(() => { | |||
if (!(typeof ref === 'object' && ref)) { | |||
return; | |||
} | |||
const { current: element } = ref; | |||
if (!element) { | |||
return; | |||
} | |||
element.indeterminate = indeterminate; | |||
}, [indeterminate, ref]); | |||
return ( | |||
<> | |||
<input | |||
{...etcProps} | |||
ref={ref} | |||
type="checkbox" | |||
id={id} | |||
className={clsx( | |||
'sr-only peer', | |||
styles['toggle-button'], | |||
)} | |||
/> | |||
<label | |||
htmlFor={id} | |||
className={clsx( | |||
'items-center justify-start rounded overflow-hidden ring-secondary/50 gap-4 leading-none select-none cursor-pointer', | |||
'peer-focus:outline-0 peer-focus:ring-4 peer-focus:ring-secondary/50', | |||
'active:ring-tertiary/50 active:ring-4', | |||
'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:ring-0', | |||
'text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary', | |||
{ | |||
'flex w-full': block, | |||
'inline-flex max-w-full align-middle': !block, | |||
}, | |||
{ | |||
'pl-2 pr-2': compact, | |||
'pl-4 pr-4': !compact, | |||
}, | |||
{ | |||
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary' : variant !== 'bare', | |||
'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
}, | |||
className, | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'w-6 h-6 block rounded border-2 p-0.5 box-border', | |||
{ | |||
'border-current': variant !== 'filled', | |||
'border-negative': variant === 'filled', | |||
'-mr-2': compact, | |||
} | |||
)} | |||
> | |||
<svg | |||
className={clsx( | |||
'w-full h-full fill-none stroke-3 linejoin-round linecap-round', | |||
{ | |||
'stroke-negative': variant === 'filled', | |||
'stroke-current': variant !== 'filled', | |||
} | |||
)} | |||
viewBox="0 0 24 24" | |||
role="presentation" | |||
> | |||
<polyline | |||
points="20 6 9 17 4 12" | |||
/> | |||
</svg> | |||
<svg | |||
className={clsx( | |||
'w-full h-full fill-none stroke-3 linejoin-round linecap-round', | |||
{ | |||
'stroke-negative': variant === 'filled', | |||
'stroke-current': variant !== 'filled', | |||
} | |||
)} | |||
viewBox="0 0 24 24" | |||
role="presentation" | |||
> | |||
<polyline | |||
points="20 12 4 12" | |||
/> | |||
</svg> | |||
</span> | |||
<span | |||
className={clsx( | |||
'contents', | |||
{ | |||
'text-current': variant !== 'filled', | |||
'text-negative': variant === 'filled', | |||
}, | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'flex-auto min-w-0', | |||
)} | |||
> | |||
<span | |||
className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded" | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
{subtext && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
{badge && ( | |||
<> | |||
<span className="sr-only"> | |||
{' - '} | |||
</span> | |||
<span | |||
data-testid="badge" | |||
> | |||
{badge} | |||
</span> | |||
</> | |||
)} | |||
</span> | |||
</label> | |||
</> | |||
) | |||
}); | |||
ToggleButton.displayName = 'ToggleButton'; |
@@ -0,0 +1,15 @@ | |||
.toggle-button + label > :first-child > :first-child { | |||
display: none; | |||
} | |||
.toggle-button:checked + label > :first-child > :first-child { | |||
display: block; | |||
} | |||
.toggle-button + label > :first-child > :first-child + * { | |||
display: none; | |||
} | |||
.toggle-button:indeterminate + label > :first-child > :first-child + * { | |||
display: block; | |||
} |
@@ -1,3 +1,121 @@ | |||
export const ToggleSwitch = () => ( | |||
<input type="checkbox" /> | |||
); | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import styles from './style.module.css'; | |||
import {ToggleButtonDerivedElement} from '@/categories/option/react'; | |||
export type ToggleSwitchDerivedElement = HTMLInputElement; | |||
export interface ToggleSwitchProps extends Omit<React.InputHTMLAttributes<ToggleSwitchDerivedElement>, 'type' | 'size'> { | |||
block?: boolean; | |||
subtext?: React.ReactNode; | |||
badge?: React.ReactNode; | |||
indeterminate?: boolean; | |||
checkedLabel?: React.ReactNode; | |||
uncheckedLabel?: React.ReactNode; | |||
} | |||
export const ToggleSwitch = React.forwardRef<ToggleSwitchDerivedElement, ToggleSwitchProps>(({ | |||
uncheckedLabel, | |||
checkedLabel, | |||
block = false, | |||
id: idProp, | |||
className, | |||
subtext, | |||
badge, | |||
style, | |||
indeterminate = false, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const defaultRef = React.useRef<ToggleButtonDerivedElement>(null); | |||
const ref = forwardedRef ?? defaultRef; | |||
const defaultId = React.useId(); | |||
const id = idProp ?? defaultId; | |||
React.useEffect(() => { | |||
if (!(typeof ref === 'object' && ref)) { | |||
return; | |||
} | |||
const { current: element } = ref; | |||
if (!element) { | |||
return; | |||
} | |||
element.indeterminate = indeterminate; | |||
}, [indeterminate, ref]); | |||
return ( | |||
<div | |||
className={clsx( | |||
'flex gap-x-4 flex-wrap', | |||
className, | |||
)} | |||
style={style} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={ref} | |||
type="checkbox" | |||
id={id} | |||
className={clsx( | |||
'sr-only peer/radio', | |||
styles['toggle-switch'], | |||
)} | |||
/> | |||
<label | |||
htmlFor={id} | |||
className="peer/children order-3 cursor-pointer peer-disabled/radio:cursor-not-allowed" | |||
> | |||
<span | |||
data-testid="children" | |||
> | |||
{checkedLabel} | |||
</span> | |||
</label> | |||
<label | |||
htmlFor={id} | |||
className="peer/children order-1 cursor-pointer peer-disabled/radio:cursor-not-allowed" | |||
> | |||
{uncheckedLabel && ( | |||
<span | |||
data-testid="uncheckedLabel" | |||
> | |||
{uncheckedLabel} | |||
</span> | |||
)} | |||
</label> | |||
<label | |||
htmlFor={id} | |||
className={clsx( | |||
'order-2 block rounded-full ring-secondary/50 overflow-hidden gap-4 leading-none select-none cursor-pointer', | |||
'peer-focus/radio:outline-0 peer-focus/radio:ring-4 peer-focus/radio:ring-secondary/50', | |||
'active:ring-tertiary/50 active:ring-4', | |||
'peer-active/children:ring-tertiary/50 peer-active/children:ring-4 peer-active/children:text-tertiary', | |||
'peer-disabled/radio:opacity-50 peer-disabled/radio:cursor-not-allowed peer-disabled/radio:ring-0', | |||
'text-primary peer-disabled/radio:text-primary peer-focus/radio:text-secondary peer-checked/radio:text-tertiary active:text-tertiary', | |||
!uncheckedLabel && '-ml-4', | |||
)} | |||
> | |||
<span> | |||
<span> | |||
<span> | |||
<span /> | |||
</span> | |||
</span> | |||
</span> | |||
</label> | |||
{subtext && ( | |||
<div | |||
className={clsx( | |||
'block w-full font-semi-expanded text-xs order-4', | |||
!uncheckedLabel && 'pl-16', | |||
uncheckedLabel && 'pt-2', | |||
)} | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</div> | |||
)} | |||
</div> | |||
) | |||
}); | |||
ToggleSwitch.displayName = 'ToggleSwitch'; |
@@ -0,0 +1,118 @@ | |||
.toggle-switch + label + label + label > :first-child { | |||
appearance: none; | |||
cursor: pointer; | |||
position: relative; | |||
overflow: hidden; | |||
height: 1.5em; | |||
width: 3em; | |||
display: block; | |||
box-sizing: border-box; | |||
border-radius: 9999px; | |||
color: rgb(var(--color-primary)); | |||
} | |||
.toggle-switch:checked + label + label + label > :first-child { | |||
color: rgb(var(--color-tertiary)); | |||
} | |||
.toggle-switch:focus + label + label + label > :first-child { | |||
color: rgb(var(--color-secondary)); | |||
} | |||
.toggle-switch + label:active + label + label > :first-child { | |||
color: rgb(var(--color-tertiary)); | |||
} | |||
.toggle-switch + label + label:active + label > :first-child { | |||
color: rgb(var(--color-tertiary)); | |||
} | |||
.toggle-switch + label + label + label:active > :first-child { | |||
color: rgb(var(--color-tertiary)); | |||
} | |||
.toggle-switch + label + label + label > :first-child > :first-child { | |||
width: 100%; | |||
height: 100%; | |||
background-color: rgb(var(--color-primary) / 50%); | |||
border-radius: 9999px; | |||
display: block; | |||
box-sizing: border-box; | |||
background-clip: content-box; | |||
padding: 0.25em; | |||
appearance: none; | |||
} | |||
.toggle-switch:checked + label + label + label > :first-child > :first-child { | |||
background-color: rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch:focus + label + label + label > :first-child > :first-child { | |||
background-color: rgb(var(--color-secondary) / 50%); | |||
} | |||
.toggle-switch + label:active + label + label > :first-child > :first-child { | |||
background-color: rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch + label + label:active + label > :first-child > :first-child { | |||
background-color: rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch + label + label + label:active > :first-child > :first-child { | |||
background-color: rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch + label + label + label > :first-child > :first-child > :first-child { | |||
appearance: none; | |||
border-radius: 9999px; | |||
display: block; | |||
width: 100%; | |||
height: 100%; | |||
margin: -0.25em; | |||
box-sizing: border-box; | |||
background-clip: content-box; | |||
} | |||
.toggle-switch + label + label + label > :first-child > :first-child > :first-child > :first-child { | |||
width: 1.5em; | |||
height: 1.5em; | |||
margin: -0.25em 0 0 0; | |||
display: block; | |||
border-radius: 9999px; | |||
background-color: currentColor; | |||
appearance: none; | |||
aspect-ratio: 1 / 1; | |||
z-index: 1; | |||
position: relative; | |||
box-shadow: -100000.5em 0 0 100000em rgb(var(--color-primary) / 50%); | |||
} | |||
.toggle-switch:checked + label + label + label > :first-child > :first-child > :first-child > :first-child { | |||
margin-left: calc(100% - 1em); | |||
} | |||
.toggle-switch:indeterminate + label + label + label > :first-child > :first-child > :first-child > :first-child { | |||
margin-left: calc(50% - 0.5em); | |||
} | |||
.toggle-switch:checked + label + label + label > :first-child > :first-child > :first-child > :first-child { | |||
box-shadow: -100000.5em 0 0 100000em rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch:focus + label + label + label > :first-child > :first-child > :first-child > :first-child { | |||
box-shadow: -100000.5em 0 0 100000em rgb(var(--color-secondary) / 50%); | |||
} | |||
.toggle-switch + label:active + label + label > :first-child > :first-child > :first-child > :first-child { | |||
box-shadow: -100000.5em 0 0 100000em rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch + label + label:active + label > :first-child > :first-child > :first-child > :first-child { | |||
box-shadow: -100000.5em 0 0 100000em rgb(var(--color-tertiary) / 50%); | |||
} | |||
.toggle-switch + label + label + label:active > :first-child > :first-child > :first-child > :first-child { | |||
box-shadow: -100000.5em 0 0 100000em rgb(var(--color-tertiary) / 50%); | |||
} | |||
@@ -1,3 +1,122 @@ | |||
export const ToggleTickBox = () => ( | |||
<input type="checkbox" /> | |||
); | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import styles from './style.module.css'; | |||
import {ToggleButtonDerivedElement} from '@/categories/option/react'; | |||
export type ToggleTickBoxDerivedElement = HTMLInputElement; | |||
export interface ToggleTickBoxProps extends Omit<React.InputHTMLAttributes<ToggleTickBoxDerivedElement>, 'type' | 'size'> { | |||
block?: boolean; | |||
subtext?: React.ReactNode; | |||
badge?: React.ReactNode; | |||
indeterminate?: boolean; | |||
} | |||
export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, ToggleTickBoxProps>(({ | |||
children, | |||
block = false, | |||
id: idProp, | |||
className, | |||
subtext, | |||
badge, | |||
style, | |||
indeterminate = false, | |||
...etcProps | |||
}, forwardedRef) => { | |||
const defaultRef = React.useRef<ToggleButtonDerivedElement>(null); | |||
const ref = forwardedRef ?? defaultRef; | |||
const defaultId = React.useId(); | |||
const id = idProp ?? defaultId; | |||
React.useEffect(() => { | |||
if (!(typeof ref === 'object' && ref)) { | |||
return; | |||
} | |||
const { current: element } = ref; | |||
if (!element) { | |||
return; | |||
} | |||
element.indeterminate = indeterminate; | |||
}, [indeterminate, ref]); | |||
return ( | |||
<div | |||
className={clsx( | |||
'flex gap-x-4 flex-wrap', | |||
className, | |||
)} | |||
style={style} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={ref} | |||
type="checkbox" | |||
id={id} | |||
className={clsx( | |||
'sr-only peer/radio', | |||
styles['toggle-tick-box'], | |||
)} | |||
/> | |||
<label | |||
htmlFor={id} | |||
className="peer/children order-2 cursor-pointer peer-disabled/radio:cursor-not-allowed" | |||
> | |||
<span | |||
data-testid="children" | |||
> | |||
{children} | |||
</span> | |||
</label> | |||
<label | |||
htmlFor={id} | |||
className={clsx( | |||
'order-1 block rounded ring-secondary/50 overflow-hidden gap-4 leading-none select-none cursor-pointer', | |||
'peer-focus/radio:outline-0 peer-focus/radio:ring-4 peer-focus/radio:ring-secondary/50', | |||
'active:ring-tertiary/50 active:ring-4', | |||
'peer-active/children:ring-tertiary/50 peer-active/children:ring-4 peer-active/children:text-tertiary', | |||
'peer-disabled/radio:opacity-50 peer-disabled/radio:cursor-not-allowed peer-disabled/radio:ring-0', | |||
'text-primary peer-disabled/radio:text-primary peer-focus/radio:text-secondary peer-checked/radio:text-tertiary active:text-tertiary', | |||
)} | |||
> | |||
<span | |||
className={clsx( | |||
'w-6 h-6 block rounded-inherit border-2 p-0.5 box-border border-current', | |||
)} | |||
> | |||
<svg | |||
className={clsx( | |||
'w-full h-full fill-none stroke-3 linejoin-round linecap-round stroke-current', | |||
)} | |||
viewBox="0 0 24 24" | |||
role="presentation" | |||
> | |||
<polyline | |||
points="20 6 9 17 4 12" | |||
/> | |||
</svg> | |||
<svg | |||
className={clsx( | |||
'w-full h-full fill-none stroke-3 linejoin-round linecap-round stroke-current', | |||
)} | |||
viewBox="0 0 24 24" | |||
role="presentation" | |||
> | |||
<polyline | |||
points="20 12 4 12" | |||
/> | |||
</svg> | |||
</span> | |||
</label> | |||
{subtext && ( | |||
<div | |||
className="block w-full font-semi-expanded text-xs pl-10 order-3" | |||
data-testid="subtext" | |||
> | |||
{subtext} | |||
</div> | |||
)} | |||
</div> | |||
) | |||
}); | |||
ToggleTickBox.displayName = 'ToggleTickBox'; |
@@ -0,0 +1,15 @@ | |||
.toggle-tick-box + label + label > :first-child > :first-child { | |||
display: none; | |||
} | |||
.toggle-tick-box:checked + label + label > :first-child > :first-child { | |||
display: block; | |||
} | |||
.toggle-tick-box + label + label > :first-child > :first-child + * { | |||
display: none; | |||
} | |||
.toggle-tick-box:indeterminate + label + label > :first-child > :first-child + * { | |||
display: block; | |||
} |
@@ -1,6 +1,5 @@ | |||
import { NextPage } from 'next'; | |||
import * as Option from '@tesseract-design/web-option-react'; | |||
import * as SelectControlBase from '@tesseract-design/web-base-selectcontrol'; | |||
import { DefaultLayout } from '@/components/DefaultLayout'; | |||
import {ReactNode} from 'react'; | |||
@@ -625,6 +624,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
variant="bare" | |||
name="RadioButton" | |||
> | |||
Button | |||
@@ -641,7 +641,7 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.RadioButton | |||
border | |||
variant="outline" | |||
block | |||
name="RadioButton" | |||
> | |||
@@ -650,7 +650,6 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.RadioButton | |||
border | |||
variant="filled" | |||
block | |||
name="RadioButton" | |||
@@ -663,6 +662,7 @@ const OptionPage: NextPage<Props> = ({ | |||
block | |||
disabled | |||
name="RadioButton" | |||
variant="bare" | |||
> | |||
Button | |||
</Option.RadioButton> | |||
@@ -679,7 +679,7 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.RadioButton | |||
border | |||
variant="outline" | |||
block | |||
disabled | |||
name="RadioButton" | |||
@@ -689,7 +689,6 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.RadioButton | |||
border | |||
variant="filled" | |||
block | |||
disabled | |||
@@ -708,7 +707,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
border | |||
variant="outline" | |||
size="small" | |||
name="RadioButton" | |||
> | |||
@@ -718,7 +717,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
border | |||
variant="filled" | |||
size="small" | |||
name="RadioButton" | |||
@@ -729,7 +727,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
border | |||
variant="outline" | |||
size="medium" | |||
name="RadioButton" | |||
> | |||
@@ -739,7 +737,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
border | |||
variant="filled" | |||
size="medium" | |||
name="RadioButton" | |||
@@ -750,7 +747,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
border | |||
variant="outline" | |||
size="large" | |||
name="RadioButton" | |||
> | |||
@@ -760,7 +757,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.RadioButton | |||
block | |||
border | |||
variant="filled" | |||
size="large" | |||
name="RadioButton" | |||
@@ -779,7 +775,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.RadioButton | |||
block | |||
compact | |||
border | |||
variant="outline" | |||
name="RadioButton" | |||
> | |||
Button | |||
@@ -789,7 +785,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.RadioButton | |||
block | |||
compact | |||
border | |||
variant="filled" | |||
name="RadioButton" | |||
> | |||
@@ -800,7 +795,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.RadioButton | |||
block | |||
compact | |||
border | |||
variant="outline" | |||
name="RadioButton" | |||
subtext={ | |||
<> | |||
@@ -815,7 +810,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.RadioButton | |||
block | |||
compact | |||
border | |||
variant="filled" | |||
name="RadioButton" | |||
subtext={ | |||
@@ -831,7 +825,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.RadioButton | |||
block | |||
compact | |||
border | |||
variant="outline" | |||
size="small" | |||
name="RadioButton" | |||
subtext={ | |||
@@ -847,7 +841,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.RadioButton | |||
block | |||
compact | |||
border | |||
variant="filled" | |||
size="small" | |||
name="RadioButton" | |||
@@ -883,203 +876,26 @@ const OptionPage: NextPage<Props> = ({ | |||
</Option.RadioTickBox> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</section> | |||
<section> | |||
<div className="container mx-auto px-4"> | |||
<h1> | |||
TagInput | |||
</h1> | |||
<div> | |||
<section> | |||
<h2> | |||
Default | |||
</h2> | |||
<div> | |||
<div className="grid md:grid-cols-2 gap-4"> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
size="small" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
size="small" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
size="large" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
size="large" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</section> | |||
<section> | |||
<h2>Alternate</h2> | |||
<div className="grid gap-4 my-4"> | |||
<div> | |||
<div className="grid md:grid-cols-2 gap-4"> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
variant="alternate" | |||
size="small" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
variant="alternate" | |||
size="small" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
variant="alternate" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
variant="alternate" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
variant="alternate" | |||
size="large" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
border | |||
block | |||
variant="alternate" | |||
size="large" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
variant="alternate" | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
<div> | |||
<Option.TagInput | |||
enhanced | |||
variant="alternate" | |||
border | |||
label="TagInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
disabled | |||
/> | |||
</div> | |||
</div> | |||
<Option.RadioTickBox | |||
block | |||
subtext={ | |||
<> | |||
<div> | |||
This is a very long text that may span a couple of lines. | |||
</div> | |||
<div> | |||
The subtext should not be included with the click area of the component. | |||
</div> | |||
</> | |||
} | |||
name="RadioButton" | |||
> | |||
RadioButton | |||
</Option.RadioTickBox> | |||
</div> | |||
</section> | |||
</div> | |||
</div> | |||
</div> | |||
</section> | |||
@@ -1095,6 +911,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div className="grid md:grid-cols-2 gap-4 my-4"> | |||
<div> | |||
<Option.ToggleButton | |||
variant="bare" | |||
block | |||
> | |||
Button | |||
@@ -1110,7 +927,7 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
border | |||
variant="outline" | |||
block | |||
> | |||
Button | |||
@@ -1118,7 +935,6 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
border | |||
variant="filled" | |||
block | |||
> | |||
@@ -1127,6 +943,7 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
variant="bare" | |||
block | |||
disabled | |||
> | |||
@@ -1144,7 +961,7 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
border | |||
variant="outline" | |||
block | |||
disabled | |||
> | |||
@@ -1153,7 +970,6 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
border | |||
variant="filled" | |||
block | |||
disabled | |||
@@ -1163,7 +979,7 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
border | |||
variant="outline" | |||
block | |||
indeterminate | |||
> | |||
@@ -1172,7 +988,6 @@ const OptionPage: NextPage<Props> = ({ | |||
</div> | |||
<div> | |||
<Option.ToggleButton | |||
border | |||
variant="filled" | |||
block | |||
indeterminate | |||
@@ -1190,7 +1005,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.ToggleButton | |||
block | |||
border | |||
variant="outline" | |||
size="small" | |||
> | |||
Button | |||
@@ -1199,7 +1014,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.ToggleButton | |||
block | |||
border | |||
variant="filled" | |||
size="small" | |||
> | |||
@@ -1209,7 +1023,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.ToggleButton | |||
block | |||
border | |||
variant="outline" | |||
size="medium" | |||
> | |||
Button | |||
@@ -1218,7 +1032,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.ToggleButton | |||
block | |||
border | |||
variant="filled" | |||
size="medium" | |||
> | |||
@@ -1228,7 +1041,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.ToggleButton | |||
block | |||
border | |||
variant="outline" | |||
size="large" | |||
> | |||
Button | |||
@@ -1237,7 +1050,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<div> | |||
<Option.ToggleButton | |||
block | |||
border | |||
variant="filled" | |||
size="large" | |||
> | |||
@@ -1255,7 +1067,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleButton | |||
block | |||
compact | |||
border | |||
variant="outline" | |||
> | |||
Button | |||
</Option.ToggleButton> | |||
@@ -1264,7 +1076,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleButton | |||
block | |||
compact | |||
border | |||
variant="filled" | |||
> | |||
Button | |||
@@ -1274,7 +1085,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleButton | |||
block | |||
compact | |||
border | |||
variant="outline" | |||
subtext={ | |||
<> | |||
Subtext | |||
@@ -1288,7 +1099,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleButton | |||
block | |||
compact | |||
border | |||
variant="filled" | |||
subtext={ | |||
<> | |||
@@ -1303,7 +1113,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleButton | |||
block | |||
compact | |||
border | |||
variant="outline" | |||
size="small" | |||
subtext={ | |||
<> | |||
@@ -1318,7 +1128,6 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleButton | |||
block | |||
compact | |||
border | |||
variant="filled" | |||
size="small" | |||
subtext={ | |||
@@ -1379,6 +1188,7 @@ const OptionPage: NextPage<Props> = ({ | |||
<Option.ToggleSwitch | |||
block | |||
subtext="Subtext" | |||
indeterminate | |||
checkedLabel="On" | |||
uncheckedLabel="Off" | |||
/> | |||
@@ -1441,8 +1251,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1452,8 +1263,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1461,8 +1273,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1471,8 +1284,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1481,8 +1295,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1492,8 +1307,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1502,8 +1318,9 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1513,8 +1330,9 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -1531,8 +1349,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1543,8 +1362,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1553,8 +1373,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1564,8 +1385,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1575,8 +1397,9 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1587,8 +1410,9 @@ const OptionPage: NextPage<Props> = ({ | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1598,8 +1422,9 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
<div> | |||
<Option.ComboBox | |||
@@ -1610,8 +1435,9 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
options={options} | |||
/> | |||
> | |||
{options} | |||
</Option.ComboBox> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -1638,6 +1464,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1648,6 +1475,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1656,6 +1484,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1665,6 +1494,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1674,6 +1504,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1684,6 +1515,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1693,6 +1525,7 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1703,6 +1536,7 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
enhanced | |||
/> | |||
</div> | |||
</div> | |||
@@ -1720,6 +1554,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1731,6 +1566,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1740,6 +1576,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1750,6 +1587,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1760,6 +1598,7 @@ const OptionPage: NextPage<Props> = ({ | |||
hint="Type anything here…" | |||
indicator="A" | |||
block | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1771,6 +1610,7 @@ const OptionPage: NextPage<Props> = ({ | |||
label="MultilineTextInput" | |||
hint="Type anything here…" | |||
indicator="A" | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1781,6 +1621,7 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
enhanced | |||
/> | |||
</div> | |||
<div> | |||
@@ -1792,6 +1633,7 @@ const OptionPage: NextPage<Props> = ({ | |||
indicator="A" | |||
block | |||
disabled | |||
enhanced | |||
/> | |||
</div> | |||
</div> | |||
@@ -65,6 +65,9 @@ module.exports = { | |||
16: '4rem', | |||
64: '16rem', | |||
}, | |||
strokeWidth: { | |||
3: '3', | |||
}, | |||
}, | |||
}, | |||
plugins: [], | |||
@@ -22,6 +22,7 @@ | |||
"@tesseract-design/web-base-textcontrol": ["./src/base/textcontrol"], | |||
"@tesseract-design/web-action-react": ["./src/categories/action/react"], | |||
"@tesseract-design/web-blob-react": ["./src/categories/blob/react"], | |||
"@tesseract-design/web-color-react": ["./src/categories/color/react"], | |||
"@tesseract-design/web-freeform-react": ["./src/categories/freeform/react"], | |||
"@tesseract-design/web-information-react": ["./src/categories/information/react"], | |||
"@tesseract-design/web-option-react": ["./src/categories/option/react"], | |||