feat(ui): centraliza estilo de filtros/busca (components/filterStyles) + SearchInput
Fonte única para os campos de filtro (inputNew/labelNew) e um componente SearchInput padrão (ícone + input arredondado, encaminha ref/props). Profissionais e Salas passam a importar do módulo (dedupe). Aplica SearchInput na busca do Orçamentos como 1ª adoção. Buscas que são autocomplete com dropdown (Pacientes, Relatórios) ficam para conversão cuidadosa caso a caso (não quebrar o dropdown). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
// Estilo PADRÃO de filtros/busca do app (fonte única). Referência visual:
|
||||
// marketplace de profissionais / alugar salas.
|
||||
import React, { forwardRef } from 'react';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
// Classes de campo e rótulo dos filtros (select/input dentro do card de filtros).
|
||||
export const inputNew = 'w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm font-semibold text-gray-700 outline-none focus:border-teal-400 focus:bg-white transition-colors';
|
||||
export const labelNew = 'text-[11px] font-bold text-gray-500 mb-1.5 block';
|
||||
|
||||
// Campo de BUSCA padrão (ícone à esquerda + input arredondado). Encaminha ref e
|
||||
// todos os props nativos (value, onChange, onKeyDown, onFocus, autoFocus, etc.).
|
||||
interface SearchInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
/** classe do wrapper (ex.: largura: 'w-full' | 'min-w-[220px]'). */
|
||||
wrapperClassName?: string;
|
||||
}
|
||||
export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
|
||||
({ wrapperClassName = 'w-full', className = '', ...rest }, ref) => (
|
||||
<div className={`relative ${wrapperClassName}`}>
|
||||
<Search size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" />
|
||||
<input
|
||||
ref={ref}
|
||||
{...rest}
|
||||
className={`w-full pl-10 pr-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium text-gray-700 outline-none focus:border-teal-400 focus:bg-white transition-colors ${className}`}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
SearchInput.displayName = 'SearchInput';
|
||||
@@ -6,6 +6,7 @@ import { useHybridBackend } from '../hooks/useHybridBackend.ts';
|
||||
import { useToast } from '../contexts/ToastContext.tsx';
|
||||
import { useConfirm } from '../contexts/ConfirmContext.tsx';
|
||||
import { PageHeader } from '../components/PageHeader.tsx';
|
||||
import { SearchInput } from '../components/filterStyles.tsx';
|
||||
import { OrcamentoModal, DEFAULT_CLAUSULAS } from './OrcamentoModal.tsx';
|
||||
|
||||
type StatusFilter = 'TODOS' | 'Ativo' | 'Rascunho' | 'Assinado' | 'Encerrado';
|
||||
@@ -148,16 +149,7 @@ export const OrcamentosView: React.FC = () => {
|
||||
|
||||
{/* Search + status filters */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 mb-4">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" size={16} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar por paciente ou título..."
|
||||
className="w-full pl-9 pr-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-2 focus:ring-emerald-100 focus:border-emerald-300 outline-none bg-white"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<SearchInput wrapperClassName="flex-1" placeholder="Buscar por paciente ou título..." value={search} onChange={e => setSearch(e.target.value)} />
|
||||
<div className="flex items-center gap-1 bg-gray-100 rounded-lg p-1 overflow-x-auto">
|
||||
{STATUS_TABS.map(s => (
|
||||
<button
|
||||
|
||||
@@ -230,9 +230,8 @@ const CAT_META: Record<CatModelo, { label: string; icon: any }> = {
|
||||
|
||||
const inputCls = 'w-full px-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold text-gray-700 outline-none focus:border-teal-400';
|
||||
const labelCls = 'text-[10px] font-black text-gray-400 uppercase tracking-widest block mb-1';
|
||||
// Estilo da busca (versão nova) — rótulos title-case e campos maiores/arredondados.
|
||||
const inputNew = 'w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm font-semibold text-gray-700 outline-none focus:border-teal-400 focus:bg-white transition-colors';
|
||||
const labelNew = 'text-[11px] font-bold text-gray-500 mb-1.5 block';
|
||||
// Estilo padrão de filtros — fonte única em components/filterStyles.
|
||||
import { inputNew, labelNew } from '../../components/filterStyles.tsx';
|
||||
|
||||
// ─── Modal: enviar proposta ──────────────────────────────────────────────────
|
||||
const PropostaModal: React.FC<{ prof: Profissional; onClose: () => void; onSent: () => void }> = ({ prof, onClose, onSent }) => {
|
||||
|
||||
@@ -108,9 +108,8 @@ const precos = (s: Sala): Array<[string, string]> => {
|
||||
|
||||
const inputCls = 'w-full px-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-bold text-gray-700 outline-none focus:border-teal-400';
|
||||
const labelCls = 'text-[10px] font-black text-gray-400 uppercase tracking-widest block mb-1';
|
||||
// Estilo da área de filtros (mesmo do marketplace de profissionais).
|
||||
const inputNew = 'w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm font-semibold text-gray-700 outline-none focus:border-teal-400 focus:bg-white transition-colors';
|
||||
const labelNew = 'text-[11px] font-bold text-gray-500 mb-1.5 block';
|
||||
// Estilo padrão de filtros — fonte única em components/filterStyles.
|
||||
import { inputNew, labelNew } from '../../components/filterStyles.tsx';
|
||||
|
||||
// ─── Modal: cadastrar / editar sala ──────────────────────────────────────────
|
||||
const SalaFormModal: React.FC<{ sala: Sala | null; onClose: () => void; onSaved: () => void }> = ({ sala, onClose, onSaved }) => {
|
||||
|
||||
Reference in New Issue
Block a user