@patreon/studio
Version:
Patreon Studio Design System
124 lines (114 loc) • 4.31 kB
JSX
'use client';
import React, { useCallback, useId, useState } from 'react';
import { styled } from 'styled-components';
import { IconError } from '~/components/Icon';
import { Tooltip } from '~/components/Tooltip';
import { VisuallyHiddenText } from '~/components/VisuallyHiddenText';
import { tokens } from '~/tokens';
import { cssForBodyText, cssForHeadingText } from '~/utilities/type-bundles';
const Autosize = styled.div `
display: inline-grid;
position: relative;
${({ $type, size }) => $type === 'body' && cssForBodyText({ size: size })}
${({ $type, size }) => $type === 'heading' && cssForHeadingText({ size: size })}
&::after {
content: attr(data-value);
visibility: hidden;
white-space: pre-wrap;
grid-area: 1 / 2;
padding: 2px 6px;
margin: 0;
height: 1px;
border: 1px solid transparent;
// we can safely inherit the font properties from the parent element
// because these live below the cssForBodyText and cssForHeadingText utils
font: inherit;
line-height: inherit;
letter-spacing: inherit;
text-transform: inherit;
${(props) => props.hasError && `padding-right: ${tokens.global.space.x24}`}
}
`;
const StyledInput = styled.input `
width: auto;
grid-area: 1 / 2;
padding: 2px 6px;
margin: 0;
background-color: transparent;
border: ${tokens.global.borderWidth.thin} solid;
border-radius: ${tokens.global.radius.sm};
color: ${tokens.global.content.regular.default};
// we can safely inherit the font properties from the parent element
// because these live below the cssForBodyText and cssForHeadingText utils
font: inherit;
line-height: inherit;
letter-spacing: inherit;
text-transform: inherit;
&::placeholder {
color: ${tokens.global.content.muted.default};
}
&:focus::placeholder {
display: none;
}
&:focus::placeholder {
color: transparent;
}
&[value='']:focus {
width: ${tokens.global.space.x4};
}
&:hover:not([aria-invalid='true']) {
border-color: ${tokens.global.border.action.hover};
}
&:focus {
background-color: ${tokens.global.bg.base.default};
}
${(props) => props.hasError &&
`
border-color: ${tokens.global.critical.surface.default};
`}
border-color: ${({ variant }) => (variant === 'visible' ? tokens.global.border.action.default : 'transparent')};
`;
const ErrorIcon = styled.div `
display: flex;
align-items: center;
height: 100%;
position: absolute;
right: 6px;
`;
/**
* @beta This component is still under construction and is subject to change
*/
function ImpliedInputBase({ error, errorPlacement, label, maxLength, name, onBlur, onChange, onFocus, placeholder, size = 'md', type = 'body', value, variant = 'default', }, ref) {
const id = useId();
const [isFocused, setIsFocused] = useState(false);
const handleChange = useCallback((e) => {
onChange(e.target.value);
}, [onChange]);
const handleFocus = useCallback(() => {
setIsFocused(true);
if (typeof onFocus === 'function') {
onFocus();
}
}, [onFocus]);
const handleBlur = useCallback(() => {
setIsFocused(false);
if (typeof onBlur === 'function') {
onBlur();
}
}, [onBlur]);
const hasError = error !== undefined;
const showVisualError = !isFocused && hasError;
return (<>
<Autosize $type={type} size={size} hasError={showVisualError} data-value={isFocused ? value : value || placeholder}>
<StyledInput type="text" onChange={handleChange} size={1} value={value} placeholder={placeholder} hasError={showVisualError} maxLength={maxLength} name={name} onBlur={handleBlur} onFocus={handleFocus} aria-label={label} aria-invalid={hasError} aria-errormessage={hasError ? id : undefined} data-tag={`inline-input-${name}`} ref={ref} variant={variant}/>
{showVisualError && (<ErrorIcon>
<Tooltip preferredPlacement={errorPlacement} textContent={error}>
<IconError size="16px" color={tokens.global.critical.surface.default}/>
</Tooltip>
</ErrorIcon>)}
</Autosize>
{hasError && <VisuallyHiddenText id={id}>{error}</VisuallyHiddenText>}
</>);
}
export const ImpliedInput = React.forwardRef(ImpliedInputBase);
//# sourceMappingURL=index.jsx.map