UNPKG

@patreon/studio

Version:

Patreon Studio Design System

124 lines (114 loc) 4.31 kB
'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