@strategicos/ui 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ import { cn } from "@strategicos/ui/utils"
15
15
 
16
16
  ## Componentes
17
17
 
18
- `alert` · `alert-dialog` · `avatar` · `badge` · `button` · `card` · `checkbox` · `dialog` · `dropdown-menu` · `input` · `label` · `pagination` · `select` · `separator` · `sheet` · `sidebar` · `skeleton` · `sonner` · `switch` · `table` · `tabs` · `textarea` · `tooltip`
18
+ `alert` · `alert-dialog` · `avatar` · `badge` · `button` · `card` · `checkbox` · `dialog` · `dropdown-menu` · `field` · `input` · `label` · `pagination` · `password-input` · `select` · `separator` · `sheet` · `sidebar` · `skeleton` · `sonner` · `switch` · `table` · `tabs` · `textarea` · `tooltip`
19
19
 
20
20
  Utilitários: `@strategicos/ui/utils` (`cn`) e `@strategicos/ui/hooks/use-mobile` (`useIsMobile`).
21
21
 
@@ -29,6 +29,23 @@ Utilitários: `@strategicos/ui/utils` (`cn`) e `@strategicos/ui/hooks/use-mobile
29
29
 
30
30
  Botões de ícone: `icon-xs` 24px, `icon-sm` 30px, `icon` 36px, `icon-lg` 42px.
31
31
 
32
+ ## Formulários
33
+
34
+ `Field` monta rótulo, descrição, controle e erro, e liga tudo ao controle de dentro (`id`, `aria-describedby`, `aria-invalid`, `required`). Funciona com `Input`, `PasswordInput`, `Textarea` e `SelectTrigger`.
35
+
36
+ ```tsx
37
+ import { Field } from "@strategicos/ui/field"
38
+ import { Input } from "@strategicos/ui/input"
39
+
40
+ <Field label="E-mail" description="Usado nos avisos" error={erro} required>
41
+ <Input type="email" value={email} onChange={e => setEmail(e.target.value)} />
42
+ </Field>
43
+ ```
44
+
45
+ - `Input` aceita `leftSection` e `rightSection` (ícones ou botões dentro do campo). Quando há seção, use `wrapperClassName` para largura e posição.
46
+ - `PasswordInput` traz o botão de mostrar/ocultar a senha.
47
+ - `Textarea autosize` cresce com o texto. Limite a altura com `max-h-*`.
48
+
32
49
  ## Variantes de status
33
50
 
34
51
  `Badge` e `Alert` aceitam `variant="info" | "success" | "warning" | "destructive"`. O Badge também aceita `default`, `secondary`, `outline`, `ghost` e `link`.
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+ type ControlProps = {
3
+ id?: string;
4
+ "aria-describedby"?: string;
5
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"];
6
+ };
7
+ /** Mescla nas props do controle o que vem do Field em volta (se houver).
8
+ * O que a tela passar explicitamente vence. `withRequired: false` para
9
+ * controles em que `required` não é atributo válido (o SelectTrigger é um
10
+ * <button>). */
11
+ declare function useFieldControl<P extends ControlProps & {
12
+ required?: boolean;
13
+ }>(props: P, { withRequired }?: {
14
+ withRequired?: boolean;
15
+ }): P;
16
+ declare function Field({ label, description, error, required, id, className, children, ...props }: Omit<React.ComponentProps<"div">, "id"> & {
17
+ label?: React.ReactNode;
18
+ description?: React.ReactNode;
19
+ /** Mensagem de erro. Quando presente, o controle fica com aria-invalid. */
20
+ error?: React.ReactNode;
21
+ /** Mostra o asterisco no rótulo e marca o controle como obrigatório. */
22
+ required?: boolean;
23
+ /** id do controle. Sem ele, um id é gerado. */
24
+ id?: string;
25
+ }): React.JSX.Element;
26
+ export { Field, useFieldControl };
27
+ //# sourceMappingURL=field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/components/field.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA4B9B,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,cAAc,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;CACtD,CAAA;AAED;;;iBAGiB;AACjB,iBAAS,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EACtE,KAAK,EAAE,CAAC,EACR,EAAE,YAAmB,EAAE,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACvD,CAAC,CAWH;AAED,iBAAS,KAAK,CAAC,EACb,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAgB,EAChB,EAAE,EACF,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG;IAC3C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,2EAA2E;IAC3E,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,qBA0CA;AAED,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,CAAA"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Label } from "./label.js";
6
+ const FieldContext = React.createContext(null);
7
+ /** Mescla nas props do controle o que vem do Field em volta (se houver).
8
+ * O que a tela passar explicitamente vence. `withRequired: false` para
9
+ * controles em que `required` não é atributo válido (o SelectTrigger é um
10
+ * <button>). */
11
+ function useFieldControl(props, { withRequired = true } = {}) {
12
+ const field = React.useContext(FieldContext);
13
+ if (!field)
14
+ return props;
15
+ const describedBy = [props["aria-describedby"], field.describedBy].filter(Boolean).join(" ") || undefined;
16
+ return {
17
+ ...props,
18
+ id: props.id ?? field.id,
19
+ "aria-describedby": describedBy,
20
+ "aria-invalid": props["aria-invalid"] ?? (field.invalid || undefined),
21
+ ...(withRequired && { required: props.required ?? (field.required || undefined) }),
22
+ };
23
+ }
24
+ function Field({ label, description, error, required = false, id, className, children, ...props }) {
25
+ const autoId = React.useId();
26
+ const controlId = id ?? autoId;
27
+ const descriptionId = description ? `${controlId}-description` : undefined;
28
+ const errorId = error ? `${controlId}-error` : undefined;
29
+ const value = React.useMemo(() => ({
30
+ id: controlId,
31
+ describedBy: [descriptionId, errorId].filter(Boolean).join(" ") || undefined,
32
+ invalid: !!error,
33
+ required,
34
+ }), [controlId, descriptionId, errorId, error, required]);
35
+ return (_jsxs("div", { "data-slot": "field", "data-invalid": error ? true : undefined, className: cn("grid content-start gap-1.5", className), ...props, children: [label && (_jsxs(Label, { htmlFor: controlId, children: [label, required && _jsx("span", { "aria-hidden": true, className: "-ml-1 text-destructive", children: "*" })] })), description && (_jsx("p", { id: descriptionId, "data-slot": "field-description", className: "-mt-0.5 text-xs leading-snug text-muted-foreground", children: description })), _jsx(FieldContext.Provider, { value: value, children: children }), error && (_jsx("p", { id: errorId, "data-slot": "field-error", className: "text-xs leading-snug text-destructive", children: error }))] }));
36
+ }
37
+ export { Field, useFieldControl };
38
+ //# sourceMappingURL=field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.js","sourceRoot":"","sources":["../../src/components/field.tsx"],"names":[],"mappings":"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"}
@@ -1,4 +1,18 @@
1
1
  import * as React from "react";
2
- declare function Input({ className, type, ...props }: React.ComponentProps<"input">): React.JSX.Element;
3
- export { Input };
2
+ /** Classes do campo. Foco e erro seguem o Mantine: só a borda muda de cor,
3
+ * sem halo em volta. */
4
+ declare const inputClassName = "h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:text-destructive aria-invalid:placeholder:text-destructive/60 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80";
5
+ type InputProps = React.ComponentProps<"input"> & {
6
+ /** Conteúdo à esquerda, dentro do campo (ex.: ícone de busca). Não recebe
7
+ * cliques: o clique passa para o campo. */
8
+ leftSection?: React.ReactNode;
9
+ /** Conteúdo à direita, dentro do campo (ex.: botão de limpar). Recebe
10
+ * cliques. */
11
+ rightSection?: React.ReactNode;
12
+ /** Classes do invólucro, que só existe quando há seção. Use para largura e
13
+ * posição no layout (flex-1, w-64…); `className` vai sempre no <input>. */
14
+ wrapperClassName?: string;
15
+ };
16
+ declare function Input({ className, type, leftSection, rightSection, wrapperClassName, ...props }: InputProps): React.JSX.Element;
17
+ export { Input, inputClassName, type InputProps };
4
18
  //# sourceMappingURL=input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/components/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,qBAY1E;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/components/input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B;yBACyB;AACzB,QAAA,MAAM,cAAc,sgBACif,CAAA;AAKrgB,KAAK,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG;IAChD;gDAC4C;IAC5C,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B;mBACe;IACf,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B;gFAC4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,qBA6BpG;AAED,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,CAAA"}
@@ -1,7 +1,17 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { cn } from "../lib/utils.js";
3
- function Input({ className, type, ...props }) {
4
- return (_jsx("input", { type: type, "data-slot": "input", className: cn("h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className), ...props }));
4
+ import { useFieldControl } from "./field.js";
5
+ /** Classes do campo. Foco e erro seguem o Mantine: só a borda muda de cor,
6
+ * sem halo em volta. */
7
+ const inputClassName = "h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:text-destructive aria-invalid:placeholder:text-destructive/60 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80";
8
+ const sectionClassName = "absolute inset-y-0 flex w-9 items-center justify-center text-muted-foreground [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
9
+ function Input({ className, type, leftSection, rightSection, wrapperClassName, ...props }) {
10
+ const controlProps = useFieldControl(props);
11
+ const input = (_jsx("input", { type: type, "data-slot": "input", className: cn(inputClassName, !!leftSection && "pl-9", !!rightSection && "pr-9", className), ...controlProps }));
12
+ if (!leftSection && !rightSection)
13
+ return input;
14
+ return (_jsxs("div", { "data-slot": "input-wrapper", className: cn("relative w-full min-w-0", wrapperClassName), children: [leftSection && (_jsx("span", { "data-slot": "input-left-section", className: cn(sectionClassName, "pointer-events-none left-0"), children: leftSection })), input, rightSection && (_jsx("span", { "data-slot": "input-right-section", className: cn(sectionClassName, "right-0"), children: rightSection }))] }));
5
15
  }
6
- export { Input };
16
+ export { Input, inputClassName };
7
17
  //# sourceMappingURL=input.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAiC;IACzE,OAAO,CACL,gBACE,IAAI,EAAE,IAAI,eACA,OAAO,EACjB,SAAS,EAAE,EAAE,CACX,2mBAA2mB,EAC3mB,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/components/input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAIZ,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5C;yBACyB;AACzB,MAAM,cAAc,GAClB,mgBAAmgB,CAAA;AAErgB,MAAM,gBAAgB,GACpB,qIAAqI,CAAA;AAcvI,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,GAAG,KAAK,EAAc;IACnG,MAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IAE3C,MAAM,KAAK,GAAG,CACZ,gBACE,IAAI,EAAE,IAAI,eACA,OAAO,EACjB,SAAS,EAAE,EAAE,CAAC,cAAc,EAAE,CAAC,CAAC,WAAW,IAAI,MAAM,EAAE,CAAC,CAAC,YAAY,IAAI,MAAM,EAAE,SAAS,CAAC,KACvF,YAAY,GAChB,CACH,CAAA;IAED,IAAI,CAAC,WAAW,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAA;IAE/C,OAAO,CACL,4BAAe,eAAe,EAAC,SAAS,EAAE,EAAE,CAAC,yBAAyB,EAAE,gBAAgB,CAAC,aACtF,WAAW,IAAI,CACd,4BAAgB,oBAAoB,EAAC,SAAS,EAAE,EAAE,CAAC,gBAAgB,EAAE,4BAA4B,CAAC,YAC/F,WAAW,GACP,CACR,EACA,KAAK,EACL,YAAY,IAAI,CACf,4BAAgB,qBAAqB,EAAC,SAAS,EAAE,EAAE,CAAC,gBAAgB,EAAE,SAAS,CAAC,YAC7E,YAAY,GACR,CACR,IACG,CACP,CAAA;AACH,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,cAAc,EAAmB,CAAA"}
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ import { type InputProps } from "./input.js";
3
+ /** Campo de senha com o botão de mostrar/ocultar à direita, como o
4
+ * PasswordInput do Mantine. Aceita as mesmas props do Input, menos `type` e
5
+ * `rightSection`, que ele controla. */
6
+ declare function PasswordInput({ disabled, ...props }: Omit<InputProps, "type" | "rightSection">): React.JSX.Element;
7
+ export { PasswordInput };
8
+ //# sourceMappingURL=password-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-input.d.ts","sourceRoot":"","sources":["../../src/components/password-input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAEnD;;wCAEwC;AACxC,iBAAS,aAAa,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAAC,qBAsBvF;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { EyeIcon, EyeOffIcon } from "lucide-react";
5
+ import { Input } from "./input.js";
6
+ /** Campo de senha com o botão de mostrar/ocultar à direita, como o
7
+ * PasswordInput do Mantine. Aceita as mesmas props do Input, menos `type` e
8
+ * `rightSection`, que ele controla. */
9
+ function PasswordInput({ disabled, ...props }) {
10
+ const [visible, setVisible] = React.useState(false);
11
+ return (_jsx(Input, { ...props, disabled: disabled, type: visible ? "text" : "password", rightSection: _jsx("button", { type: "button", onClick: () => setVisible(v => !v), disabled: disabled, "aria-label": visible ? "Ocultar senha" : "Mostrar senha", "aria-pressed": visible, className: "flex size-7 cursor-pointer items-center justify-center rounded-md transition-colors outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50", children: visible ? _jsx(EyeOffIcon, {}) : _jsx(EyeIcon, {}) }) }));
12
+ }
13
+ export { PasswordInput };
14
+ //# sourceMappingURL=password-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-input.js","sourceRoot":"","sources":["../../src/components/password-input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAA;AAEnD;;wCAEwC;AACxC,SAAS,aAAa,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAA6C;IACtF,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEnD,OAAO,CACL,KAAC,KAAK,OACA,KAAK,EACT,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,EACnC,YAAY,EACV,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAClC,QAAQ,EAAE,QAAQ,gBACN,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,kBACzC,OAAO,EACrB,SAAS,EAAC,uOAAuO,YAEhP,OAAO,CAAC,CAAC,CAAC,KAAC,UAAU,KAAG,CAAC,CAAC,CAAC,KAAC,OAAO,KAAG,GAChC,GAEX,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAA;AAKpD,iBAAS,MAAM,CAAC,EACd,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAQpD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAEpD;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,IAAgB,EAChB,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,GAAG;IACxD,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACxB,qBAqBA;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAmB,EACnB,KAAe,EACf,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,qBAyBtD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAQpD;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,WAAW,EACX,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IAGrD,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC9B,qBAyBA;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,SAAS,CAAC,qBAQxD;AAED,iBAAS,oBAAoB,CAAC,EAC5B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,cAAc,CAAC,qBAc7D;AAED,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,gBAAgB,CAAC,qBAc/D;AAED,OAAO,EACL,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,WAAW,GACZ,CAAA"}
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAA;AAMpD,iBAAS,MAAM,CAAC,EACd,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,qBAEnD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAQpD;AAED,iBAAS,WAAW,CAAC,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAEpD;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,IAAgB,EAChB,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,GAAG;IACxD,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACxB,qBAqBA;AAED,iBAAS,aAAa,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAmB,EACnB,KAAe,EACf,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,qBAyBtD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAQpD;AAED,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,WAAW,EACX,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IAGrD,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC9B,qBAyBA;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,SAAS,CAAC,qBAQxD;AAED,iBAAS,oBAAoB,CAAC,EAC5B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,cAAc,CAAC,qBAc7D;AAED,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,gBAAgB,CAAC,qBAc/D;AAED,OAAO,EACL,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,WAAW,GACZ,CAAA"}
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Select as SelectPrimitive } from "radix-ui";
4
4
  import { cn } from "../lib/utils.js";
5
+ import { useFieldControl } from "./field.js";
5
6
  import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
6
7
  function Select({ ...props }) {
7
8
  return _jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
@@ -13,7 +14,7 @@ function SelectValue({ ...props }) {
13
14
  return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
14
15
  }
15
16
  function SelectTrigger({ className, size = "default", children, ...props }) {
16
- return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn("flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2.5 pl-3 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-7.5 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 opacity-70" }) })] }));
17
+ return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn("flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2.5 pl-3 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring data-[state=open]:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-7.5 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...useFieldControl(props, { withRequired: false }), children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 opacity-70" }) })] }));
17
18
  }
18
19
  function SelectContent({ className, children, position = "popper", align = "start", ...props }) {
19
20
  return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, align: align, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { "data-position": position, className: cn("p-1 data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)", position === "popper" && ""), children: children }), _jsx(SelectScrollDownButton, {})] }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/components/select.tsx"],"names":[],"mappings":"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"}
@@ -1,15 +1,21 @@
1
1
  import * as React from "react";
2
2
  /**
3
3
  * Campo multilinha. Espelha os tokens do <Input> — mesma borda, mesmo raio,
4
- * mesmo anel de foco, mesmo tratamento de desabilitado e de aria-invalid — para
5
- * que um campo de texto longo não pareça de outro sistema ao lado de um curto.
4
+ * mesmo foco, mesmo tratamento de desabilitado e de aria-invalid — para que um
5
+ * campo de texto longo não pareça de outro sistema ao lado de um curto.
6
6
  *
7
7
  * Difere do Input só no que a multilinha exige: sem altura fixa, com padding
8
8
  * vertical maior e `leading-relaxed`.
9
9
  *
10
+ * `autosize`: a altura acompanha o texto (CSS `field-sizing: content`, sem
11
+ * JS). Limite a altura com `max-h-*` no className. Navegadores sem suporte
12
+ * (Firefox, hoje) mantêm a altura de `rows`.
13
+ *
10
14
  * Não confundir com o InlineTextarea do gerenciador: aquele é um editor inline
11
15
  * (sem borda até o hover, salva no blur), um padrão deliberadamente diferente.
12
16
  */
13
- declare function Textarea({ className, ...props }: React.ComponentProps<"textarea">): React.JSX.Element;
17
+ declare function Textarea({ className, autosize, ...props }: React.ComponentProps<"textarea"> & {
18
+ autosize?: boolean;
19
+ }): React.JSX.Element;
14
20
  export { Textarea };
15
21
  //# sourceMappingURL=textarea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../src/components/textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;GAUG;AACH,iBAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,qBAW1E;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../src/components/textarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B;;;;;;;;;;;;;;GAcG;AACH,iBAAS,QAAQ,CAAC,EAChB,SAAS,EACT,QAAgB,EAChB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,qBAa3D;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -1,18 +1,25 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { cn } from "../lib/utils.js";
4
+ import { useFieldControl } from "./field.js";
3
5
  /**
4
6
  * Campo multilinha. Espelha os tokens do <Input> — mesma borda, mesmo raio,
5
- * mesmo anel de foco, mesmo tratamento de desabilitado e de aria-invalid — para
6
- * que um campo de texto longo não pareça de outro sistema ao lado de um curto.
7
+ * mesmo foco, mesmo tratamento de desabilitado e de aria-invalid — para que um
8
+ * campo de texto longo não pareça de outro sistema ao lado de um curto.
7
9
  *
8
10
  * Difere do Input só no que a multilinha exige: sem altura fixa, com padding
9
11
  * vertical maior e `leading-relaxed`.
10
12
  *
13
+ * `autosize`: a altura acompanha o texto (CSS `field-sizing: content`, sem
14
+ * JS). Limite a altura com `max-h-*` no className. Navegadores sem suporte
15
+ * (Firefox, hoje) mantêm a altura de `rows`.
16
+ *
11
17
  * Não confundir com o InlineTextarea do gerenciador: aquele é um editor inline
12
18
  * (sem borda até o hover, salva no blur), um padrão deliberadamente diferente.
13
19
  */
14
- function Textarea({ className, ...props }) {
15
- return (_jsx("textarea", { "data-slot": "textarea", className: cn("w-full min-w-0 resize-none rounded-lg border border-input bg-transparent px-3 py-2 text-base leading-relaxed transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className), ...props }));
20
+ function Textarea({ className, autosize = false, ...props }) {
21
+ const controlProps = useFieldControl(props);
22
+ return (_jsx("textarea", { "data-slot": "textarea", className: cn("w-full min-w-0 resize-none rounded-lg border border-input bg-transparent px-3 py-2 text-base leading-relaxed transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:text-destructive aria-invalid:placeholder:text-destructive/60 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80", autosize && "field-sizing-content min-h-16 overflow-y-auto", className), ...controlProps }));
16
23
  }
17
24
  export { Textarea };
18
25
  //# sourceMappingURL=textarea.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../src/components/textarea.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC;;;;;;;;;;GAUG;AACH,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAoC;IACzE,OAAO,CACL,gCACY,UAAU,EACpB,SAAS,EAAE,EAAE,CACX,ohBAAohB,EACphB,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.js","sourceRoot":"","sources":["../../src/components/textarea.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAIZ,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;;;;;;;;;;GAcG;AACH,SAAS,QAAQ,CAAC,EAChB,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,GAAG,KAAK,EACkD;IAC1D,MAAM,YAAY,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IAC3C,OAAO,CACL,gCACY,UAAU,EACpB,SAAS,EAAE,EAAE,CACX,4aAA4a,EAC5a,QAAQ,IAAI,+CAA+C,EAC3D,SAAS,CACV,KACG,YAAY,GAChB,CACH,CAAA;AACH,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strategicos/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Componentes React da Strategicos (shadcn + Radix + Tailwind v4).",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -0,0 +1,119 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../lib/utils.js"
6
+ import { Label } from "./label.js"
7
+
8
+ /**
9
+ * Invólucro de um campo de formulário, como o Input.Wrapper do Mantine:
10
+ * rótulo, descrição, o controle e a mensagem de erro, nessa ordem.
11
+ *
12
+ * O controle dentro dele (Input, Textarea, PasswordInput, SelectTrigger) é
13
+ * ligado automaticamente: recebe o `id` do rótulo, o `aria-describedby` com a
14
+ * descrição e o erro, o `aria-invalid` quando há erro e o `required`. Nada
15
+ * disso precisa ser repetido na tela.
16
+ *
17
+ * <Field label="E-mail" description="Usado nos avisos" error={erro} required>
18
+ * <Input type="email" value={email} onChange={…} />
19
+ * </Field>
20
+ */
21
+
22
+ type FieldContextValue = {
23
+ id: string
24
+ describedBy?: string
25
+ invalid: boolean
26
+ required: boolean
27
+ }
28
+
29
+ const FieldContext = React.createContext<FieldContextValue | null>(null)
30
+
31
+ type ControlProps = {
32
+ id?: string
33
+ "aria-describedby"?: string
34
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"]
35
+ }
36
+
37
+ /** Mescla nas props do controle o que vem do Field em volta (se houver).
38
+ * O que a tela passar explicitamente vence. `withRequired: false` para
39
+ * controles em que `required` não é atributo válido (o SelectTrigger é um
40
+ * <button>). */
41
+ function useFieldControl<P extends ControlProps & { required?: boolean }>(
42
+ props: P,
43
+ { withRequired = true }: { withRequired?: boolean } = {}
44
+ ): P {
45
+ const field = React.useContext(FieldContext)
46
+ if (!field) return props
47
+ const describedBy = [props["aria-describedby"], field.describedBy].filter(Boolean).join(" ") || undefined
48
+ return {
49
+ ...props,
50
+ id: props.id ?? field.id,
51
+ "aria-describedby": describedBy,
52
+ "aria-invalid": props["aria-invalid"] ?? (field.invalid || undefined),
53
+ ...(withRequired && { required: props.required ?? (field.required || undefined) }),
54
+ }
55
+ }
56
+
57
+ function Field({
58
+ label,
59
+ description,
60
+ error,
61
+ required = false,
62
+ id,
63
+ className,
64
+ children,
65
+ ...props
66
+ }: Omit<React.ComponentProps<"div">, "id"> & {
67
+ label?: React.ReactNode
68
+ description?: React.ReactNode
69
+ /** Mensagem de erro. Quando presente, o controle fica com aria-invalid. */
70
+ error?: React.ReactNode
71
+ /** Mostra o asterisco no rótulo e marca o controle como obrigatório. */
72
+ required?: boolean
73
+ /** id do controle. Sem ele, um id é gerado. */
74
+ id?: string
75
+ }) {
76
+ const autoId = React.useId()
77
+ const controlId = id ?? autoId
78
+ const descriptionId = description ? `${controlId}-description` : undefined
79
+ const errorId = error ? `${controlId}-error` : undefined
80
+
81
+ const value = React.useMemo<FieldContextValue>(
82
+ () => ({
83
+ id: controlId,
84
+ describedBy: [descriptionId, errorId].filter(Boolean).join(" ") || undefined,
85
+ invalid: !!error,
86
+ required,
87
+ }),
88
+ [controlId, descriptionId, errorId, error, required]
89
+ )
90
+
91
+ return (
92
+ <div
93
+ data-slot="field"
94
+ data-invalid={error ? true : undefined}
95
+ className={cn("grid content-start gap-1.5", className)}
96
+ {...props}
97
+ >
98
+ {label && (
99
+ <Label htmlFor={controlId}>
100
+ {label}
101
+ {required && <span aria-hidden className="-ml-1 text-destructive">*</span>}
102
+ </Label>
103
+ )}
104
+ {description && (
105
+ <p id={descriptionId} data-slot="field-description" className="-mt-0.5 text-xs leading-snug text-muted-foreground">
106
+ {description}
107
+ </p>
108
+ )}
109
+ <FieldContext.Provider value={value}>{children}</FieldContext.Provider>
110
+ {error && (
111
+ <p id={errorId} data-slot="field-error" className="text-xs leading-snug text-destructive">
112
+ {error}
113
+ </p>
114
+ )}
115
+ </div>
116
+ )
117
+ }
118
+
119
+ export { Field, useFieldControl }
@@ -1,19 +1,59 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
 
3
5
  import { cn } from "../lib/utils.js"
6
+ import { useFieldControl } from "./field.js"
4
7
 
5
- function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
- return (
8
+ /** Classes do campo. Foco e erro seguem o Mantine: só a borda muda de cor,
9
+ * sem halo em volta. */
10
+ const inputClassName =
11
+ "h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:text-destructive aria-invalid:placeholder:text-destructive/60 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80"
12
+
13
+ const sectionClassName =
14
+ "absolute inset-y-0 flex w-9 items-center justify-center text-muted-foreground [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
15
+
16
+ type InputProps = React.ComponentProps<"input"> & {
17
+ /** Conteúdo à esquerda, dentro do campo (ex.: ícone de busca). Não recebe
18
+ * cliques: o clique passa para o campo. */
19
+ leftSection?: React.ReactNode
20
+ /** Conteúdo à direita, dentro do campo (ex.: botão de limpar). Recebe
21
+ * cliques. */
22
+ rightSection?: React.ReactNode
23
+ /** Classes do invólucro, que só existe quando há seção. Use para largura e
24
+ * posição no layout (flex-1, w-64…); `className` vai sempre no <input>. */
25
+ wrapperClassName?: string
26
+ }
27
+
28
+ function Input({ className, type, leftSection, rightSection, wrapperClassName, ...props }: InputProps) {
29
+ const controlProps = useFieldControl(props)
30
+
31
+ const input = (
7
32
  <input
8
33
  type={type}
9
34
  data-slot="input"
10
- className={cn(
11
- "h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-3 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
12
- className
13
- )}
14
- {...props}
35
+ className={cn(inputClassName, !!leftSection && "pl-9", !!rightSection && "pr-9", className)}
36
+ {...controlProps}
15
37
  />
16
38
  )
39
+
40
+ if (!leftSection && !rightSection) return input
41
+
42
+ return (
43
+ <div data-slot="input-wrapper" className={cn("relative w-full min-w-0", wrapperClassName)}>
44
+ {leftSection && (
45
+ <span data-slot="input-left-section" className={cn(sectionClassName, "pointer-events-none left-0")}>
46
+ {leftSection}
47
+ </span>
48
+ )}
49
+ {input}
50
+ {rightSection && (
51
+ <span data-slot="input-right-section" className={cn(sectionClassName, "right-0")}>
52
+ {rightSection}
53
+ </span>
54
+ )}
55
+ </div>
56
+ )
17
57
  }
18
58
 
19
- export { Input }
59
+ export { Input, inputClassName, type InputProps }
@@ -0,0 +1,35 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { EyeIcon, EyeOffIcon } from "lucide-react"
5
+
6
+ import { Input, type InputProps } from "./input.js"
7
+
8
+ /** Campo de senha com o botão de mostrar/ocultar à direita, como o
9
+ * PasswordInput do Mantine. Aceita as mesmas props do Input, menos `type` e
10
+ * `rightSection`, que ele controla. */
11
+ function PasswordInput({ disabled, ...props }: Omit<InputProps, "type" | "rightSection">) {
12
+ const [visible, setVisible] = React.useState(false)
13
+
14
+ return (
15
+ <Input
16
+ {...props}
17
+ disabled={disabled}
18
+ type={visible ? "text" : "password"}
19
+ rightSection={
20
+ <button
21
+ type="button"
22
+ onClick={() => setVisible(v => !v)}
23
+ disabled={disabled}
24
+ aria-label={visible ? "Ocultar senha" : "Mostrar senha"}
25
+ aria-pressed={visible}
26
+ className="flex size-7 cursor-pointer items-center justify-center rounded-md transition-colors outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50"
27
+ >
28
+ {visible ? <EyeOffIcon /> : <EyeIcon />}
29
+ </button>
30
+ }
31
+ />
32
+ )
33
+ }
34
+
35
+ export { PasswordInput }
@@ -4,6 +4,7 @@ import * as React from "react"
4
4
  import { Select as SelectPrimitive } from "radix-ui"
5
5
 
6
6
  import { cn } from "../lib/utils.js"
7
+ import { useFieldControl } from "./field.js"
7
8
  import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
8
9
 
9
10
  function Select({
@@ -44,10 +45,10 @@ function SelectTrigger({
44
45
  data-slot="select-trigger"
45
46
  data-size={size}
46
47
  className={cn(
47
- "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2.5 pl-3 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-7.5 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
48
+ "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2.5 pl-3 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring data-[state=open]:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-7.5 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
48
49
  className
49
50
  )}
50
- {...props}
51
+ {...useFieldControl(props, { withRequired: false })}
51
52
  >
52
53
  {children}
53
54
  <SelectPrimitive.Icon asChild>
@@ -1,27 +1,40 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
 
3
5
  import { cn } from "../lib/utils.js"
6
+ import { useFieldControl } from "./field.js"
4
7
 
5
8
  /**
6
9
  * Campo multilinha. Espelha os tokens do <Input> — mesma borda, mesmo raio,
7
- * mesmo anel de foco, mesmo tratamento de desabilitado e de aria-invalid — para
8
- * que um campo de texto longo não pareça de outro sistema ao lado de um curto.
10
+ * mesmo foco, mesmo tratamento de desabilitado e de aria-invalid — para que um
11
+ * campo de texto longo não pareça de outro sistema ao lado de um curto.
9
12
  *
10
13
  * Difere do Input só no que a multilinha exige: sem altura fixa, com padding
11
14
  * vertical maior e `leading-relaxed`.
12
15
  *
16
+ * `autosize`: a altura acompanha o texto (CSS `field-sizing: content`, sem
17
+ * JS). Limite a altura com `max-h-*` no className. Navegadores sem suporte
18
+ * (Firefox, hoje) mantêm a altura de `rows`.
19
+ *
13
20
  * Não confundir com o InlineTextarea do gerenciador: aquele é um editor inline
14
21
  * (sem borda até o hover, salva no blur), um padrão deliberadamente diferente.
15
22
  */
16
- function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
23
+ function Textarea({
24
+ className,
25
+ autosize = false,
26
+ ...props
27
+ }: React.ComponentProps<"textarea"> & { autosize?: boolean }) {
28
+ const controlProps = useFieldControl(props)
17
29
  return (
18
30
  <textarea
19
31
  data-slot="textarea"
20
32
  className={cn(
21
- "w-full min-w-0 resize-none rounded-lg border border-input bg-transparent px-3 py-2 text-base leading-relaxed transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
33
+ "w-full min-w-0 resize-none rounded-lg border border-input bg-transparent px-3 py-2 text-base leading-relaxed transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:text-destructive aria-invalid:placeholder:text-destructive/60 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80",
34
+ autosize && "field-sizing-content min-h-16 overflow-y-auto",
22
35
  className
23
36
  )}
24
- {...props}
37
+ {...controlProps}
25
38
  />
26
39
  )
27
40
  }