- Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3) - Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor - Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS - Configurare adresă platformă externalizată (build args / .env / config.js) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
2.5 KiB
TypeScript
91 lines
2.5 KiB
TypeScript
import React from 'react';
|
|
import styled from '@emotion/styled';
|
|
import { colors, typography, spacing, borderRadius, animation } from '../../theme';
|
|
|
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
label?: string;
|
|
error?: string;
|
|
helperText?: string;
|
|
}
|
|
|
|
export const Input: React.FC<InputProps> = ({
|
|
label,
|
|
error,
|
|
helperText,
|
|
id,
|
|
...props
|
|
}) => {
|
|
const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
return (
|
|
<Container>
|
|
{label && <Label htmlFor={inputId}>{label}</Label>}
|
|
<StyledInput id={inputId} hasError={!!error} {...props} />
|
|
{error && <ErrorText>{error}</ErrorText>}
|
|
{helperText && !error && <HelperText>{helperText}</HelperText>}
|
|
</Container>
|
|
);
|
|
};
|
|
|
|
const Container = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
`;
|
|
|
|
const Label = styled.label`
|
|
display: block;
|
|
font-family: ${typography.fontFamily.primary};
|
|
font-size: ${typography.fontSize.sm};
|
|
font-weight: ${typography.fontWeight.medium};
|
|
color: var(--fg-secondary);
|
|
margin-bottom: ${spacing.sm}px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
`;
|
|
|
|
const StyledInput = styled.input<{ hasError: boolean }>`
|
|
width: 100%;
|
|
padding: ${spacing.md}px ${spacing.lg}px;
|
|
background: var(--bg-surface);
|
|
border: 1px solid ${props => props.hasError ? colors.cautionRed : 'var(--border-default)'};
|
|
border-radius: 10px;
|
|
color: var(--fg-primary);
|
|
font-family: ${typography.fontFamily.primary};
|
|
font-size: ${typography.fontSize.base};
|
|
transition: all ${animation.duration.normal} ${animation.easing.default};
|
|
|
|
&::placeholder {
|
|
color: var(--fg-muted);
|
|
}
|
|
|
|
&:focus {
|
|
outline: none;
|
|
border-color: ${props => props.hasError ? colors.cautionRed : 'var(--accent)'};
|
|
background: ${props => props.hasError ? 'rgba(230, 57, 70, 0.05)' : 'var(--bg-surface)'};
|
|
box-shadow: 0 0 0 3px ${props => props.hasError ? 'rgba(230, 57, 70, 0.1)' : 'rgba(0, 145, 152, 0.12)'};
|
|
}
|
|
|
|
&:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
background: var(--bg-hover);
|
|
}
|
|
`;
|
|
|
|
const ErrorText = styled.span`
|
|
display: block;
|
|
font-family: ${typography.fontFamily.primary};
|
|
font-size: ${typography.fontSize.sm};
|
|
color: ${colors.cautionRed};
|
|
margin-top: ${spacing.xs}px;
|
|
`;
|
|
|
|
const HelperText = styled.span`
|
|
display: block;
|
|
font-family: ${typography.fontFamily.primary};
|
|
font-size: ${typography.fontSize.sm};
|
|
color: var(--fg-muted);
|
|
margin-top: ${spacing.xs}px;
|
|
font-style: italic;
|
|
`;
|