@12-apps/ui 6.0.1 → 6.0.2

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.
@@ -4,8 +4,9 @@ import {
4
4
  StackedModalActions,
5
5
  StackedModalContent,
6
6
  useModalStack
7
- } from "../chunk-XDP4HLQ3.js";
7
+ } from "../chunk-QZZVHY6U.js";
8
8
  import "../chunk-TSPG6TWK.js";
9
+ import "../chunk-MQLM43E4.js";
9
10
  import "../chunk-PZ5AY32C.js";
10
11
  export {
11
12
  ModalStackProvider,
@@ -7,8 +7,9 @@ import {
7
7
  foldText,
8
8
  leavesOf,
9
9
  summarizeSelection
10
- } from "../chunk-LBYA4ZPT.js";
10
+ } from "../chunk-R4TYXKDD.js";
11
11
  import "../chunk-FD4W36WR.js";
12
+ import "../chunk-MQLM43E4.js";
12
13
  import "../chunk-PZ5AY32C.js";
13
14
  export {
14
15
  CategorySelect,
@@ -3,6 +3,9 @@ import {
3
3
  FormLabel,
4
4
  FormMessage
5
5
  } from "../chunk-FD4W36WR.js";
6
+ import {
7
+ stackedOverlayZIndex
8
+ } from "../chunk-MQLM43E4.js";
6
9
  import "../chunk-PZ5AY32C.js";
7
10
 
8
11
  // src/components/form/CreatableSelect/CreatableSelect.tsx
@@ -138,7 +141,7 @@ function CreatableSelect({
138
141
  clearOnBlur: true,
139
142
  "data-testid": dataTestId,
140
143
  noOptionsText,
141
- slotProps: { popper: { sx: { zIndex: (theme) => theme.zIndex.modal + 100 } } },
144
+ slotProps: { popper: { sx: { zIndex: stackedOverlayZIndex } } },
142
145
  getOptionLabel: (option) => typeof option === "string" ? option : option.label,
143
146
  isOptionEqualToValue: (option, current) => option.value === current.value,
144
147
  renderOption: renderIndentedOption,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/form/CreatableSelect/CreatableSelect.tsx"],"sourcesContent":["'use client';\n\nimport { alpha } from '@mui/material';\nimport { Autocomplete, createFilterOptions } from '@mui/material';\nimport type { AutocompleteRenderInputParams } from '@mui/material';\nimport { CircularProgress } from '@mui/material';\nimport { TextField } from '@mui/material';\nimport { useId, useMemo } from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../Form';\nimport type { CreatableSelectOption, CreatableSelectProps } from './CreatableSelect.types';\nimport type { SizeValue } from '../../../tokens/scales';\n\n/** Internal option shape: a real option, or the synthetic \"create new\" row. */\ninterface InternalOption extends CreatableSelectOption {\n /** True for the synthetic row that triggers {@link CreatableSelectProps.onCreate}. */\n isCreate?: boolean;\n}\n\n/** Prefix that marks a synthetic create-row value so it can never collide with a real one. */\nconst CREATE_PREFIX = ' create:';\n\nconst filterOptions = createFilterOptions<InternalOption>();\n\n/** How far one nesting level indents a dropdown row, in px. */\nconst INDENT_STEP = 16;\n\n/** `sm`/`md` → MUI's own scale, the same mapping {@link Input} uses. */\n// All five house stops onto MUI's two. `xs`/`sm` draw small; `md` and up draw\n// medium, because an autocomplete field has no third or fourth height to give.\nconst MUI_SIZE = { xs: 'small', sm: 'small', md: 'medium', lg: 'medium', xl: 'medium' } as const;\n\n/** Match the outlined border treatment of `Input` so siblings look identical. */\nconst fieldSx = {\n '& .MuiOutlinedInput-root': {\n '& fieldset': {\n borderColor: (theme: { palette: { divider: string } }) => alpha(theme.palette.divider, 0.23),\n },\n '&:hover fieldset': { borderColor: 'primary.main' },\n '&.Mui-focused fieldset': { borderColor: 'primary.main', borderWidth: 2 },\n '&.Mui-error fieldset': { borderColor: 'error.main' },\n },\n} as const;\n\n/**\n * The filter that appends the synthetic \"create\" row when the typed text matches\n * no existing option. Built per render from the two props it closes over.\n */\nfunction buildFilter(\n onCreate: CreatableSelectProps['onCreate'],\n createOptionLabel: (input: string) => string,\n) {\n return (available: InternalOption[], params: { inputValue: string }): InternalOption[] => {\n const filtered = filterOptions(available, params as Parameters<typeof filterOptions>[1]);\n const input = params.inputValue.trim();\n const alreadyExists = available.some(\n (option) => option.label.toLowerCase() === input.toLowerCase(),\n );\n if (input && !alreadyExists && onCreate) {\n filtered.push({\n value: `${CREATE_PREFIX}${input}`,\n label: createOptionLabel(input),\n isCreate: true,\n });\n }\n return filtered;\n };\n}\n\n/**\n * A nested option is indented on its ROW; the label itself stays the bare name,\n * so the closed field never shows the tree drawing (see `depth`).\n */\nfunction renderIndentedOption(\n props: React.HTMLAttributes<HTMLLIElement>,\n option: InternalOption,\n): React.JSX.Element {\n const { key, ...rest } = props as typeof props & { key?: string };\n return (\n <li\n key={key ?? option.value}\n {...rest}\n style={{ paddingLeft: INDENT_STEP * (1 + (option.depth ?? 0)) }}\n >\n {option.label}\n </li>\n );\n}\n\n/** The text field Autocomplete renders, with the library's border + a loading adornment. */\nfunction renderField(\n params: AutocompleteRenderInputParams,\n field: {\n inputId: string;\n placeholder?: string;\n error?: string;\n loading: boolean;\n size: SizeValue;\n dataTestId: string;\n },\n): React.JSX.Element {\n return (\n <TextField\n {...params}\n id={field.inputId}\n placeholder={field.placeholder}\n error={Boolean(field.error)}\n size={MUI_SIZE[field.size]}\n sx={fieldSx}\n inputProps={{ ...params.inputProps, 'data-testid': `${field.dataTestId}-input` }}\n slotProps={{\n input: {\n ...params.InputProps,\n endAdornment: (\n <>\n {field.loading ? <CircularProgress color=\"inherit\" size={18} /> : null}\n {params.InputProps.endAdornment}\n </>\n ),\n },\n }}\n />\n );\n}\n\n/**\n * The option list, plus the selected OBJECT MUI wants, from the controlled value.\n * `depth` is carried through: it is what {@link renderIndentedOption} draws.\n */\nfunction useInternalOptions(\n options: CreatableSelectOption[],\n value: string | null,\n): { items: InternalOption[]; selected: InternalOption | null } {\n const items = useMemo<InternalOption[]>(\n () => options.map(({ value: optionValue, label, depth }) => ({\n value: optionValue,\n label,\n depth,\n })),\n [options],\n );\n const selected = useMemo<InternalOption | null>(\n () => items.find((option) => option.value === value) ?? null,\n [items, value],\n );\n return { items, selected };\n}\n\n/** Route a picked row: cleared, the synthetic create row, or a real option. */\nfunction handlePick(\n next: InternalOption | null,\n onChange: CreatableSelectProps['onChange'],\n onCreate: CreatableSelectProps['onCreate'],\n): void {\n if (!next) {\n onChange(null);\n return;\n }\n if (next.isCreate) {\n void onCreate?.(next.value.slice(CREATE_PREFIX.length));\n return;\n }\n onChange(next.value);\n}\n\n/**\n * A single-select, searchable combobox that can create a new option on the fly.\n *\n * Type to filter {@link CreatableSelectProps.options}; when the typed text matches\n * no option and {@link CreatableSelectProps.onCreate} is provided, a \"create\" row\n * appears and selecting it (Enter or click) calls `onCreate` with the raw text —\n * the parent persists it, then feeds the new option back via `options`/`value`.\n *\n * Chrome matches the library's other form fields: an external {@link FormLabel}\n * above the control (not a floating label), the same outlined border treatment as\n * {@link Input} and the same `sm`/`md` scale, so it lines up with sibling fields\n * in a grid row.\n */\nexport function CreatableSelect({\n options,\n value,\n onChange,\n onCreate,\n label,\n error,\n placeholder,\n createOptionLabel = (input) => `Criar \"${input}\"`,\n noOptionsText,\n disabled = false,\n loading = false,\n fullWidth = true,\n size = 'md',\n dataTestId = 'creatable-select',\n}: CreatableSelectProps): React.JSX.Element {\n const inputId = useId();\n const { items, selected } = useInternalOptions(options, value);\n\n return (\n <FormControl fullWidth={fullWidth} error={Boolean(error)}>\n {label && (\n <FormLabel htmlFor={inputId} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Autocomplete<InternalOption, false, false, false>\n value={selected}\n options={items}\n disabled={disabled}\n loading={loading}\n fullWidth={fullWidth}\n size={MUI_SIZE[size]}\n handleHomeEndKeys\n selectOnFocus\n clearOnBlur\n data-testid={dataTestId}\n noOptionsText={noOptionsText}\n // Lift the dropdown above stacked modals: the default popup z-index is\n // `modal` (1300), but a stacked sheet at depth ≥2 sits at 1300 + depth*10,\n // which would otherwise cover the options. +100 clears ~10 stack levels.\n slotProps={{ popper: { sx: { zIndex: (theme) => theme.zIndex.modal + 100 } } }}\n getOptionLabel={(option) => (typeof option === 'string' ? option : option.label)}\n isOptionEqualToValue={(option, current) => option.value === current.value}\n renderOption={renderIndentedOption}\n filterOptions={buildFilter(onCreate, createOptionLabel)}\n onChange={(_event, next) => handlePick(next, onChange, onCreate)}\n renderInput={(params) =>\n renderField(params, { inputId, placeholder, error, loading, size, dataTestId })\n }\n />\n {error && <FormMessage error dataTestId={`${dataTestId}-message`}>{error}</FormMessage>}\n </FormControl>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/form/CreatableSelect/CreatableSelect.tsx"],"sourcesContent":["'use client';\n\nimport { alpha } from '@mui/material';\nimport { Autocomplete, createFilterOptions } from '@mui/material';\nimport type { AutocompleteRenderInputParams } from '@mui/material';\nimport { CircularProgress } from '@mui/material';\nimport { TextField } from '@mui/material';\nimport { useId, useMemo } from 'react';\n\nimport { stackedOverlayZIndex } from '../../../tokens/layers';\nimport { FormControl, FormLabel, FormMessage } from '../Form';\nimport type { CreatableSelectOption, CreatableSelectProps } from './CreatableSelect.types';\nimport type { SizeValue } from '../../../tokens/scales';\n\n/** Internal option shape: a real option, or the synthetic \"create new\" row. */\ninterface InternalOption extends CreatableSelectOption {\n /** True for the synthetic row that triggers {@link CreatableSelectProps.onCreate}. */\n isCreate?: boolean;\n}\n\n/** Prefix that marks a synthetic create-row value so it can never collide with a real one. */\nconst CREATE_PREFIX = ' create:';\n\nconst filterOptions = createFilterOptions<InternalOption>();\n\n/** How far one nesting level indents a dropdown row, in px. */\nconst INDENT_STEP = 16;\n\n/** `sm`/`md` → MUI's own scale, the same mapping {@link Input} uses. */\n// All five house stops onto MUI's two. `xs`/`sm` draw small; `md` and up draw\n// medium, because an autocomplete field has no third or fourth height to give.\nconst MUI_SIZE = { xs: 'small', sm: 'small', md: 'medium', lg: 'medium', xl: 'medium' } as const;\n\n/** Match the outlined border treatment of `Input` so siblings look identical. */\nconst fieldSx = {\n '& .MuiOutlinedInput-root': {\n '& fieldset': {\n borderColor: (theme: { palette: { divider: string } }) => alpha(theme.palette.divider, 0.23),\n },\n '&:hover fieldset': { borderColor: 'primary.main' },\n '&.Mui-focused fieldset': { borderColor: 'primary.main', borderWidth: 2 },\n '&.Mui-error fieldset': { borderColor: 'error.main' },\n },\n} as const;\n\n/**\n * The filter that appends the synthetic \"create\" row when the typed text matches\n * no existing option. Built per render from the two props it closes over.\n */\nfunction buildFilter(\n onCreate: CreatableSelectProps['onCreate'],\n createOptionLabel: (input: string) => string,\n) {\n return (available: InternalOption[], params: { inputValue: string }): InternalOption[] => {\n const filtered = filterOptions(available, params as Parameters<typeof filterOptions>[1]);\n const input = params.inputValue.trim();\n const alreadyExists = available.some(\n (option) => option.label.toLowerCase() === input.toLowerCase(),\n );\n if (input && !alreadyExists && onCreate) {\n filtered.push({\n value: `${CREATE_PREFIX}${input}`,\n label: createOptionLabel(input),\n isCreate: true,\n });\n }\n return filtered;\n };\n}\n\n/**\n * A nested option is indented on its ROW; the label itself stays the bare name,\n * so the closed field never shows the tree drawing (see `depth`).\n */\nfunction renderIndentedOption(\n props: React.HTMLAttributes<HTMLLIElement>,\n option: InternalOption,\n): React.JSX.Element {\n const { key, ...rest } = props as typeof props & { key?: string };\n return (\n <li\n key={key ?? option.value}\n {...rest}\n style={{ paddingLeft: INDENT_STEP * (1 + (option.depth ?? 0)) }}\n >\n {option.label}\n </li>\n );\n}\n\n/** The text field Autocomplete renders, with the library's border + a loading adornment. */\nfunction renderField(\n params: AutocompleteRenderInputParams,\n field: {\n inputId: string;\n placeholder?: string;\n error?: string;\n loading: boolean;\n size: SizeValue;\n dataTestId: string;\n },\n): React.JSX.Element {\n return (\n <TextField\n {...params}\n id={field.inputId}\n placeholder={field.placeholder}\n error={Boolean(field.error)}\n size={MUI_SIZE[field.size]}\n sx={fieldSx}\n inputProps={{ ...params.inputProps, 'data-testid': `${field.dataTestId}-input` }}\n slotProps={{\n input: {\n ...params.InputProps,\n endAdornment: (\n <>\n {field.loading ? <CircularProgress color=\"inherit\" size={18} /> : null}\n {params.InputProps.endAdornment}\n </>\n ),\n },\n }}\n />\n );\n}\n\n/**\n * The option list, plus the selected OBJECT MUI wants, from the controlled value.\n * `depth` is carried through: it is what {@link renderIndentedOption} draws.\n */\nfunction useInternalOptions(\n options: CreatableSelectOption[],\n value: string | null,\n): { items: InternalOption[]; selected: InternalOption | null } {\n const items = useMemo<InternalOption[]>(\n () => options.map(({ value: optionValue, label, depth }) => ({\n value: optionValue,\n label,\n depth,\n })),\n [options],\n );\n const selected = useMemo<InternalOption | null>(\n () => items.find((option) => option.value === value) ?? null,\n [items, value],\n );\n return { items, selected };\n}\n\n/** Route a picked row: cleared, the synthetic create row, or a real option. */\nfunction handlePick(\n next: InternalOption | null,\n onChange: CreatableSelectProps['onChange'],\n onCreate: CreatableSelectProps['onCreate'],\n): void {\n if (!next) {\n onChange(null);\n return;\n }\n if (next.isCreate) {\n void onCreate?.(next.value.slice(CREATE_PREFIX.length));\n return;\n }\n onChange(next.value);\n}\n\n/**\n * A single-select, searchable combobox that can create a new option on the fly.\n *\n * Type to filter {@link CreatableSelectProps.options}; when the typed text matches\n * no option and {@link CreatableSelectProps.onCreate} is provided, a \"create\" row\n * appears and selecting it (Enter or click) calls `onCreate` with the raw text —\n * the parent persists it, then feeds the new option back via `options`/`value`.\n *\n * Chrome matches the library's other form fields: an external {@link FormLabel}\n * above the control (not a floating label), the same outlined border treatment as\n * {@link Input} and the same `sm`/`md` scale, so it lines up with sibling fields\n * in a grid row.\n */\nexport function CreatableSelect({\n options,\n value,\n onChange,\n onCreate,\n label,\n error,\n placeholder,\n createOptionLabel = (input) => `Criar \"${input}\"`,\n noOptionsText,\n disabled = false,\n loading = false,\n fullWidth = true,\n size = 'md',\n dataTestId = 'creatable-select',\n}: CreatableSelectProps): React.JSX.Element {\n const inputId = useId();\n const { items, selected } = useInternalOptions(options, value);\n\n return (\n <FormControl fullWidth={fullWidth} error={Boolean(error)}>\n {label && (\n <FormLabel htmlFor={inputId} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Autocomplete<InternalOption, false, false, false>\n value={selected}\n options={items}\n disabled={disabled}\n loading={loading}\n fullWidth={fullWidth}\n size={MUI_SIZE[size]}\n handleHomeEndKeys\n selectOnFocus\n clearOnBlur\n data-testid={dataTestId}\n noOptionsText={noOptionsText}\n // Lift the dropdown above stacked modals: the default popup z-index is\n // `modal` (1300), but a stacked sheet at depth ≥2 sits at 1300 + depth*10,\n // which would otherwise cover the options. The height is the token's\n // now — this component used to be the only place the rule was written\n // down, which is how `CategorySelect` came to be missing it (12-57).\n slotProps={{ popper: { sx: { zIndex: stackedOverlayZIndex } } }}\n getOptionLabel={(option) => (typeof option === 'string' ? option : option.label)}\n isOptionEqualToValue={(option, current) => option.value === current.value}\n renderOption={renderIndentedOption}\n filterOptions={buildFilter(onCreate, createOptionLabel)}\n onChange={(_event, next) => handlePick(next, onChange, onCreate)}\n renderInput={(params) =>\n renderField(params, { inputId, placeholder, error, loading, size, dataTestId })\n }\n />\n {error && <FormMessage error dataTestId={`${dataTestId}-message`}>{error}</FormMessage>}\n </FormControl>\n );\n}\n"],"mappings":"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","names":[]}
@@ -12,7 +12,7 @@ import {
12
12
  } from "../chunk-5ETQ3NX5.js";
13
13
  import {
14
14
  CategorySelect
15
- } from "../chunk-LBYA4ZPT.js";
15
+ } from "../chunk-R4TYXKDD.js";
16
16
  import {
17
17
  Input
18
18
  } from "../chunk-OOKQ2LMM.js";
@@ -26,6 +26,7 @@ import "../chunk-BA4RHQ3B.js";
26
26
  import {
27
27
  Button
28
28
  } from "../chunk-ANNZ3PLI.js";
29
+ import "../chunk-MQLM43E4.js";
29
30
  import {
30
31
  __export
31
32
  } from "../chunk-PZ5AY32C.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/form/total-form/FormContainer.tsx","../../src/components/form/total-form/FormContext.tsx","../../src/components/form/total-form/FormErrorSnackbar.tsx","../../src/components/form/total-form/SubmitButton.tsx","../../src/components/form/total-form/Fields/index.ts","../../src/components/form/total-form/Fields/CategoryField.tsx","../../src/components/form/total-form/Fields/CepField.tsx","../../src/components/form/total-form/Fields/TextField.tsx","../../src/components/form/total-form/Fields/SelectField.tsx","../../src/components/form/total-form/Fields/ToggleField.tsx"],"sourcesContent":["'use client';\n\nimport { validateFields } from '@12-apps/forms-core';\nimport type { ValidatorSchema } from '@12-apps/forms-core';\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { Form } from '../Form';\n\nimport { FormContext } from './FormContext';\nimport type { TotalFormContextValue } from './FormContext';\n\n/** Imperative helpers handed to {@link FormContainerProps.onSubmit}. */\nexport interface FormSubmitHelpers {\n /**\n * Set (highlight) per-field error messages after a submit — e.g. mapping a\n * server failure back to the exact offending field. Pass `{}` to clear. Each\n * error auto-clears when the user edits that field.\n */\n setFieldErrors: (errors: Record<string, string | undefined>) => void;\n}\n\n/** Props for {@link FormContainer}. */\nexport interface FormContainerProps<T extends Record<string, string>> {\n /** Initial value for every field, keyed by field name. */\n initialValues: T;\n /** Per-field validators run on submit. Omit to skip validation. */\n schema?: ValidatorSchema<T>;\n /**\n * Called with the validated values once the form passes validation. Receives\n * {@link FormSubmitHelpers} so it can highlight fields on a server failure.\n */\n onSubmit: (values: T, helpers: FormSubmitHelpers) => void | Promise<void>;\n /** Field components, the submit button, and the error snackbar. */\n children: React.ReactNode;\n /** Test id applied to the underlying `<form>`. Defaults to `total-form`. */\n dataTestId?: string;\n}\n\n/**\n * Stateful, domain-agnostic form container ported from the gym-app\n * `total-form`. Holds values/errors/submitting in {@link FormContext}, runs the\n * field validators from `@12-apps/forms-core` on submit, and only calls\n * `onSubmit` with the validated values when no field errors remain. Uses no\n * zod and no react-hook-form.\n *\n * @typeParam T - The form's value shape (a record of string fields).\n */\nexport function FormContainer<T extends Record<string, string>>({\n initialValues,\n schema,\n onSubmit,\n children,\n dataTestId = 'total-form',\n}: FormContainerProps<T>): React.ReactElement {\n const [values, setValues] = useState<T>(initialValues);\n const [errors, setErrors] = useState<Record<string, string | undefined>>({});\n const [submitting, setSubmitting] = useState(false);\n\n const setFieldValue = useCallback<TotalFormContextValue['setFieldValue']>((name, value) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n setErrors((prev) => {\n if (!prev[name]) return prev;\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const nextErrors = schema ? validateFields(values, schema) : {};\n setErrors(nextErrors);\n if (Object.values(nextErrors).some(Boolean)) return;\n\n setSubmitting(true);\n try {\n // Replace the error map so the helper's contract holds: passing `{}`\n // clears, and a later field-less failure can't leave a stale highlight.\n // `onSubmit` only runs once client validation passes, so there are no\n // client errors here to preserve.\n await onSubmit(values, {\n setFieldErrors: (fieldErrors) => setErrors(fieldErrors),\n });\n } finally {\n setSubmitting(false);\n }\n },\n [onSubmit, schema, values],\n );\n\n const contextValue = useMemo<TotalFormContextValue>(\n () => ({ values, errors, submitting, setFieldValue }),\n [errors, setFieldValue, submitting, values],\n );\n\n return (\n <FormContext.Provider value={contextValue}>\n <Form dataTestId={dataTestId} onSubmit={handleSubmit} noValidate>\n {children}\n </Form>\n </FormContext.Provider>\n );\n}\n","'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Shape of the value exposed by the `total-form` {@link FormContext}.\n *\n * Field values and errors are keyed by field name. The container owns the\n * state; fields and controls read from and write to it through\n * {@link useFormContext}.\n */\nexport interface TotalFormContextValue {\n /** Current value of every field, keyed by field name. */\n values: Record<string, string>;\n /** First validation error per field (absent when the field is valid). */\n errors: Record<string, string | undefined>;\n /** Whether the form's `onSubmit` handler is currently running. */\n submitting: boolean;\n /** Updates a single field's value and clears any resolved error for it. */\n setFieldValue: (name: string, value: string) => void;\n}\n\n/** React context backing the `total-form` layer. `null` outside a container. */\nexport const FormContext = createContext<TotalFormContextValue | null>(null);\n\n/**\n * Reads the nearest {@link FormContext}.\n *\n * @throws If called outside a `FormContainer`.\n * @returns The current form context value.\n */\nexport function useFormContext(): TotalFormContextValue {\n const context = useContext(FormContext);\n if (!context) {\n throw new Error('useFormContext must be used within a <FormContainer>');\n }\n return context;\n}\n","'use client';\n\nimport React from 'react';\n\nimport { Toast } from '../../feedback/Toast';\nimport { useFormContext } from './FormContext';\n\n/** Props for {@link FormErrorSnackbar}. */\nexport interface FormErrorSnackbarProps {\n /** Message shown when the form has field errors. */\n message?: string;\n}\n\n/**\n * Surfaces form-level validation failures. Thin wrapper over the existing\n * `@12-apps/ui` `Toast`: renders nothing while the form is valid and shows an\n * error toast (testid `total-form-error-snackbar`) once any field has an error.\n */\nexport function FormErrorSnackbar({\n message = 'Please fix the highlighted fields and try again.',\n}: FormErrorSnackbarProps): React.ReactElement | null {\n const { errors } = useFormContext();\n const hasErrors = Object.values(errors).some(Boolean);\n\n if (!hasErrors) return null;\n\n return (\n <Toast\n dataTestId=\"total-form-error-snackbar\"\n variant=\"error\"\n message={message}\n closable={false}\n />\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { Button } from '../Button';\nimport { useFormContext } from './FormContext';\n\n/** Props for {@link SubmitButton}. */\nexport interface SubmitButtonProps {\n /** Button label. */\n children: React.ReactNode;\n /** Test id; defaults to `total-form-submit`. Override when a page renders more than one form. */\n dataTestId?: string;\n}\n\n/**\n * The height of a form input control (see `Input` — `.MuiInputBase-root`), so a\n * submit button placed inline with fields lines up with them instead of sitting\n * shorter. Kept in sync with that value by hand.\n */\nconst FIELD_HEIGHT = 56;\n\n/**\n * Submit control wired to {@link useFormContext}. Wraps the existing `@12-apps/ui`\n * `Button` as a `type=\"submit\"` button (testid `total-form-submit`) and\n * reflects the form's `submitting` state via `loading`/`disabled`.\n *\n * It matches the form field height and never wraps its label, so it aligns with\n * an adjacent input on the same row (and stays full-width when the row stacks).\n */\nexport function SubmitButton({\n children,\n dataTestId = 'total-form-submit',\n}: SubmitButtonProps): React.ReactElement {\n const { submitting } = useFormContext();\n\n return (\n <Button\n type=\"submit\"\n dataTestId={dataTestId}\n loading={submitting}\n disabled={submitting}\n // `Button` replaces (not merges) its size styles with this `sx`, so restate\n // the md padding/font here alongside the field-height + no-wrap alignment.\n sx={{ height: FIELD_HEIGHT, px: 2, fontSize: '1rem', flexShrink: 0, whiteSpace: 'nowrap' }}\n >\n {children}\n </Button>\n );\n}\n","export { CategoryField } from './CategoryField';\nexport type { CategoryFieldProps } from './CategoryField';\nexport { CepField } from './CepField';\nexport type { CepFieldProps, CepFills } from './CepField';\nexport { TextField } from './TextField';\nexport type { TextFieldProps } from './TextField';\nexport { SelectField } from './SelectField';\nexport type { SelectFieldProps } from './SelectField';\nexport { ToggleField } from './ToggleField';\nexport type { ToggleFieldProps } from './ToggleField';\n","'use client';\n\nimport React from 'react';\n\nimport { CategorySelect } from '../../CategorySelect';\nimport type { CategorySelectOption } from '../../CategorySelect';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link CategoryField}. */\nexport interface CategoryFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** The categories to choose from, flat, each carrying its `parentId`. */\n options: CategorySelectOption[];\n /** Optional visible label rendered above the control. */\n label?: string;\n /** Placeholder shown while nothing is chosen. */\n placeholder?: string;\n /** Lets a top-level category be chosen in its own right (default: `true`). */\n allowParentSelection?: boolean;\n}\n\n/**\n * Category picker bound to {@link useFormContext}.\n *\n * The sibling {@link SelectField} flattens a category tree into one list, which\n * is the thing the hierarchical picker exists to stop. Use this wherever the\n * field's options nest.\n *\n * Form values are strings, so \"nothing chosen\" travels as `\"\"` — mapped to and\n * from the control's `null` here rather than making every caller do it.\n */\nexport function CategoryField({\n name,\n options,\n label,\n placeholder,\n allowParentSelection = true,\n}: CategoryFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n\n return (\n <CategorySelect\n mode=\"single\"\n fullWidth\n label={label}\n placeholder={placeholder}\n options={options}\n allowParentSelection={allowParentSelection}\n value={values[name] ? values[name] : null}\n error={error}\n onChange={(next) => setFieldValue(name, next ?? '')}\n dataTestId={`total-form-field-${name}`}\n />\n );\n}\n","'use client';\n\nimport React, { useCallback, useMemo, useRef } from 'react';\n\nimport { CepField as BaseCepField, type CepAddress } from '../../CepField';\nimport { useFormContext } from '../FormContext';\n\n/** The resolved address parts a form can receive. */\ntype CepPart = 'street' | 'neighborhood' | 'city' | 'state';\n\n/**\n * Which form field receives each resolved part. Omit a part to leave it alone —\n * a form with no bairro column simply doesn't map `neighborhood`.\n */\nexport type CepFills = Partial<Record<CepPart, string>>;\n\n/** Props for the `total-form` {@link CepField}. */\nexport interface CepFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Optional visible label. Defaults to `CEP`. */\n label?: string;\n /** Resolve a complete CEP. Omit for a masked input with no lookup. */\n lookup?: (cep: string) => Promise<CepAddress | null>;\n /** Maps each resolved address part onto a field in THIS form. */\n fills?: CepFills;\n /** Milliseconds to wait after the last keystroke before looking up. */\n debounceMs?: number;\n}\n\nconst PARTS: readonly CepPart[] = ['street', 'neighborhood', 'city', 'state'];\n\n/** Ownership-aware writes into sibling form fields. */\ninterface OwnedAutofill {\n /** Write one field, where `''` means \"no value for this part\". */\n write: (field: string, value: string) => void;\n /** Blank every field this hook still owns. */\n clearOwned: () => void;\n}\n\n/**\n * The honesty rule, in one place: this hook remembers exactly what it wrote,\n * writes only into fields that are empty or still hold its own value, and drops\n * the ownership record when it blanks one. That single invariant is what lets\n * autofill replace itself freely while never touching the user's typing.\n */\nfunction useOwnedAutofill(): OwnedAutofill {\n const { values, setFieldValue } = useFormContext();\n // What we last auto-filled per target field, so a later lookup may replace\n // its own value while a manual edit is preserved.\n const autoFilledRef = useRef<Record<string, string>>({});\n // Read values at call time without making them a dependency of the callback.\n const valuesRef = useRef(values);\n valuesRef.current = values;\n\n const write = useCallback(\n (field: string, value: string) => {\n const current = (valuesRef.current[field] ?? '').trim();\n const ours = autoFilledRef.current[field] === current;\n if (current !== '' && !ours) return;\n setFieldValue(field, value);\n if (value) {\n autoFilledRef.current[field] = value;\n } else {\n delete autoFilledRef.current[field];\n }\n },\n [setFieldValue],\n );\n\n const clearOwned = useCallback(() => {\n // Object.keys snapshots, so `write` may delete entries as we iterate.\n for (const field of Object.keys(autoFilledRef.current)) {\n write(field, '');\n }\n }, [write]);\n\n return useMemo(() => ({ write, clearOwned }), [write, clearOwned]);\n}\n\n/**\n * CEP field bound to {@link useFormContext}: masks and validates the CEP, then\n * writes the resolved logradouro/bairro/cidade/UF straight into the sibling\n * fields named by `fills`.\n *\n * It will not overwrite work the user has done. A target field is only written\n * when it is empty, or when it still holds the exact value THIS component put\n * there — so correcting the CEP re-fills the address, but a hand-edited street\n * survives the next lookup.\n *\n * @example\n * ```tsx\n * <Fields.CepField\n * name=\"postalCode\"\n * lookup={lookupCep}\n * fills={{ street: 'addressLine1', neighborhood: 'neighborhood', city: 'city', state: 'state' }}\n * />\n * ```\n */\nexport function CepField({\n name,\n label,\n lookup,\n fills,\n debounceMs,\n}: CepFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const { write, clearOwned } = useOwnedAutofill();\n\n const handleResolved = useCallback(\n (address: CepAddress) => {\n if (!fills) return;\n // Every mapped part is written on EVERY resolve, absent ones as ''. A\n // \"CEP geral\" (a small município's catch-all) carries no logradouro, so\n // skipping absent parts would leave the previous CEP's street here.\n for (const part of PARTS) {\n const field = fills[part];\n if (field) write(field, address[part] ?? '');\n }\n },\n [fills, write],\n );\n\n const handleChange = useCallback(\n (masked: string) => setFieldValue(name, masked),\n [name, setFieldValue],\n );\n\n return (\n <BaseCepField\n name={name}\n label={label}\n value={values[name] ?? ''}\n error={errors[name]}\n onChange={handleChange}\n onLookup={lookup}\n onResolved={handleResolved}\n onNotFound={clearOwned}\n debounceMs={debounceMs}\n dataTestId={`total-form-field-${name}`}\n />\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { Input } from '../../Input';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link TextField}. */\nexport interface TextFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Optional visible label rendered above the input. */\n label?: string;\n /** Native input type (e.g. `email`, `password`). Defaults to `text`. */\n type?: string;\n /** Placeholder shown when the field is empty. */\n placeholder?: string;\n}\n\n/**\n * Text input field bound to {@link useFormContext}. Composes the existing\n * `@12-apps/ui` `Form*` presentational primitives and the `Input` control,\n * reading its value/error from context and writing changes back through it.\n *\n * Renders its error via `total-form-field-<name>-message` and the input via\n * `total-form-field-<name>`.\n */\nexport function TextField({ name, label, type = 'text', placeholder }: TextFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Input\n id={name}\n name={name}\n type={type}\n placeholder={placeholder}\n value={values[name] ?? ''}\n error={Boolean(error)}\n data-testid={testId}\n onChange={(event) => setFieldValue(name, event.target.value)}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { Select } from '../../Select';\nimport type { SelectOption } from '../../Select';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link SelectField}. */\nexport interface SelectFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Selectable options. */\n options: SelectOption[];\n /** Optional visible label rendered above the select. */\n label?: string;\n /** Placeholder shown when no option is selected. */\n placeholder?: string;\n}\n\n/**\n * Select field bound to {@link useFormContext}. Composes the existing\n * `@12-apps/ui` `Form*` primitives and the `Select` control, reading its\n * value/error from context and writing the chosen value back through it.\n *\n * Renders its error via `total-form-field-<name>-message` and the select via\n * `total-form-field-<name>`.\n */\nexport function SelectField({ name, options, label, placeholder }: SelectFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Select\n options={options}\n placeholder={placeholder}\n value={values[name] ?? ''}\n error={Boolean(error)}\n data-testid={testId}\n onChange={(event) => setFieldValue(name, String(event.target.value))}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { ToggleGroup } from '../../ToggleGroup';\nimport type { ToggleOption } from '../../ToggleGroup';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link ToggleField}. */\nexport interface ToggleFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** The mutually-exclusive options (one is always selected). */\n options: ToggleOption[];\n /** Optional visible label rendered above the toggle. */\n label?: string;\n}\n\n/**\n * Exclusive toggle (segmented control) bound to {@link useFormContext} — a\n * label-height-matched alternative to {@link SelectField} for a short, always-set\n * enum. Composes the `Form*` primitives and the `ToggleGroup` control, reading\n * its value from context and writing the chosen value back through it. Ignores a\n * deselect (clicking the active segment) so a value is always kept.\n *\n * Renders its error via `total-form-field-<name>-message` and the toggle via\n * `total-form-field-<name>` (each segment at `total-form-field-<name>-item-<value>`).\n */\nexport function ToggleField({ name, options, label }: ToggleFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <ToggleGroup\n variant=\"exclusive\"\n options={options}\n value={values[name] ?? ''}\n fullWidth\n dataTestId={testId}\n // Match the input field height so the toggle lines up in a form row.\n sx={{ '& .MuiToggleButtonGroup-grouped': { height: 56 } }}\n onChange={(_event, next) => {\n if (typeof next === 'string') setFieldValue(name, next);\n }}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n"],"mappings":"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","names":["jsx","jsx","CepField","jsx","useCallback","useMemo","jsx","useCallback","useMemo","CepField","jsx","jsx","jsxs","jsx","jsxs"]}
1
+ {"version":3,"sources":["../../src/components/form/total-form/FormContainer.tsx","../../src/components/form/total-form/FormContext.tsx","../../src/components/form/total-form/FormErrorSnackbar.tsx","../../src/components/form/total-form/SubmitButton.tsx","../../src/components/form/total-form/Fields/index.ts","../../src/components/form/total-form/Fields/CategoryField.tsx","../../src/components/form/total-form/Fields/CepField.tsx","../../src/components/form/total-form/Fields/TextField.tsx","../../src/components/form/total-form/Fields/SelectField.tsx","../../src/components/form/total-form/Fields/ToggleField.tsx"],"sourcesContent":["'use client';\n\nimport { validateFields } from '@12-apps/forms-core';\nimport type { ValidatorSchema } from '@12-apps/forms-core';\nimport React, { useCallback, useMemo, useState } from 'react';\n\nimport { Form } from '../Form';\n\nimport { FormContext } from './FormContext';\nimport type { TotalFormContextValue } from './FormContext';\n\n/** Imperative helpers handed to {@link FormContainerProps.onSubmit}. */\nexport interface FormSubmitHelpers {\n /**\n * Set (highlight) per-field error messages after a submit — e.g. mapping a\n * server failure back to the exact offending field. Pass `{}` to clear. Each\n * error auto-clears when the user edits that field.\n */\n setFieldErrors: (errors: Record<string, string | undefined>) => void;\n}\n\n/** Props for {@link FormContainer}. */\nexport interface FormContainerProps<T extends Record<string, string>> {\n /** Initial value for every field, keyed by field name. */\n initialValues: T;\n /** Per-field validators run on submit. Omit to skip validation. */\n schema?: ValidatorSchema<T>;\n /**\n * Called with the validated values once the form passes validation. Receives\n * {@link FormSubmitHelpers} so it can highlight fields on a server failure.\n */\n onSubmit: (values: T, helpers: FormSubmitHelpers) => void | Promise<void>;\n /** Field components, the submit button, and the error snackbar. */\n children: React.ReactNode;\n /** Test id applied to the underlying `<form>`. Defaults to `total-form`. */\n dataTestId?: string;\n}\n\n/**\n * Stateful, domain-agnostic form container ported from the gym-app\n * `total-form`. Holds values/errors/submitting in {@link FormContext}, runs the\n * field validators from `@12-apps/forms-core` on submit, and only calls\n * `onSubmit` with the validated values when no field errors remain. Uses no\n * zod and no react-hook-form.\n *\n * @typeParam T - The form's value shape (a record of string fields).\n */\nexport function FormContainer<T extends Record<string, string>>({\n initialValues,\n schema,\n onSubmit,\n children,\n dataTestId = 'total-form',\n}: FormContainerProps<T>): React.ReactElement {\n const [values, setValues] = useState<T>(initialValues);\n const [errors, setErrors] = useState<Record<string, string | undefined>>({});\n const [submitting, setSubmitting] = useState(false);\n\n const setFieldValue = useCallback<TotalFormContextValue['setFieldValue']>((name, value) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n setErrors((prev) => {\n if (!prev[name]) return prev;\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const nextErrors = schema ? validateFields(values, schema) : {};\n setErrors(nextErrors);\n if (Object.values(nextErrors).some(Boolean)) return;\n\n setSubmitting(true);\n try {\n // Replace the error map so the helper's contract holds: passing `{}`\n // clears, and a later field-less failure can't leave a stale highlight.\n // `onSubmit` only runs once client validation passes, so there are no\n // client errors here to preserve.\n await onSubmit(values, {\n setFieldErrors: (fieldErrors) => setErrors(fieldErrors),\n });\n } finally {\n setSubmitting(false);\n }\n },\n [onSubmit, schema, values],\n );\n\n const contextValue = useMemo<TotalFormContextValue>(\n () => ({ values, errors, submitting, setFieldValue }),\n [errors, setFieldValue, submitting, values],\n );\n\n return (\n <FormContext.Provider value={contextValue}>\n <Form dataTestId={dataTestId} onSubmit={handleSubmit} noValidate>\n {children}\n </Form>\n </FormContext.Provider>\n );\n}\n","'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Shape of the value exposed by the `total-form` {@link FormContext}.\n *\n * Field values and errors are keyed by field name. The container owns the\n * state; fields and controls read from and write to it through\n * {@link useFormContext}.\n */\nexport interface TotalFormContextValue {\n /** Current value of every field, keyed by field name. */\n values: Record<string, string>;\n /** First validation error per field (absent when the field is valid). */\n errors: Record<string, string | undefined>;\n /** Whether the form's `onSubmit` handler is currently running. */\n submitting: boolean;\n /** Updates a single field's value and clears any resolved error for it. */\n setFieldValue: (name: string, value: string) => void;\n}\n\n/** React context backing the `total-form` layer. `null` outside a container. */\nexport const FormContext = createContext<TotalFormContextValue | null>(null);\n\n/**\n * Reads the nearest {@link FormContext}.\n *\n * @throws If called outside a `FormContainer`.\n * @returns The current form context value.\n */\nexport function useFormContext(): TotalFormContextValue {\n const context = useContext(FormContext);\n if (!context) {\n throw new Error('useFormContext must be used within a <FormContainer>');\n }\n return context;\n}\n","'use client';\n\nimport React from 'react';\n\nimport { Toast } from '../../feedback/Toast';\nimport { useFormContext } from './FormContext';\n\n/** Props for {@link FormErrorSnackbar}. */\nexport interface FormErrorSnackbarProps {\n /** Message shown when the form has field errors. */\n message?: string;\n}\n\n/**\n * Surfaces form-level validation failures. Thin wrapper over the existing\n * `@12-apps/ui` `Toast`: renders nothing while the form is valid and shows an\n * error toast (testid `total-form-error-snackbar`) once any field has an error.\n */\nexport function FormErrorSnackbar({\n message = 'Please fix the highlighted fields and try again.',\n}: FormErrorSnackbarProps): React.ReactElement | null {\n const { errors } = useFormContext();\n const hasErrors = Object.values(errors).some(Boolean);\n\n if (!hasErrors) return null;\n\n return (\n <Toast\n dataTestId=\"total-form-error-snackbar\"\n variant=\"error\"\n message={message}\n closable={false}\n />\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { Button } from '../Button';\nimport { useFormContext } from './FormContext';\n\n/** Props for {@link SubmitButton}. */\nexport interface SubmitButtonProps {\n /** Button label. */\n children: React.ReactNode;\n /** Test id; defaults to `total-form-submit`. Override when a page renders more than one form. */\n dataTestId?: string;\n}\n\n/**\n * The height of a form input control (see `Input` — `.MuiInputBase-root`), so a\n * submit button placed inline with fields lines up with them instead of sitting\n * shorter. Kept in sync with that value by hand.\n */\nconst FIELD_HEIGHT = 56;\n\n/**\n * Submit control wired to {@link useFormContext}. Wraps the existing `@12-apps/ui`\n * `Button` as a `type=\"submit\"` button (testid `total-form-submit`) and\n * reflects the form's `submitting` state via `loading`/`disabled`.\n *\n * It matches the form field height and never wraps its label, so it aligns with\n * an adjacent input on the same row (and stays full-width when the row stacks).\n */\nexport function SubmitButton({\n children,\n dataTestId = 'total-form-submit',\n}: SubmitButtonProps): React.ReactElement {\n const { submitting } = useFormContext();\n\n return (\n <Button\n type=\"submit\"\n dataTestId={dataTestId}\n loading={submitting}\n disabled={submitting}\n // `Button` replaces (not merges) its size styles with this `sx`, so restate\n // the md padding/font here alongside the field-height + no-wrap alignment.\n sx={{ height: FIELD_HEIGHT, px: 2, fontSize: '1rem', flexShrink: 0, whiteSpace: 'nowrap' }}\n >\n {children}\n </Button>\n );\n}\n","export { CategoryField } from './CategoryField';\nexport type { CategoryFieldProps } from './CategoryField';\nexport { CepField } from './CepField';\nexport type { CepFieldProps, CepFills } from './CepField';\nexport { TextField } from './TextField';\nexport type { TextFieldProps } from './TextField';\nexport { SelectField } from './SelectField';\nexport type { SelectFieldProps } from './SelectField';\nexport { ToggleField } from './ToggleField';\nexport type { ToggleFieldProps } from './ToggleField';\n","'use client';\n\nimport React from 'react';\n\nimport { CategorySelect } from '../../CategorySelect';\nimport type { CategorySelectOption } from '../../CategorySelect';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link CategoryField}. */\nexport interface CategoryFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** The categories to choose from, flat, each carrying its `parentId`. */\n options: CategorySelectOption[];\n /** Optional visible label rendered above the control. */\n label?: string;\n /** Placeholder shown while nothing is chosen. */\n placeholder?: string;\n /** Lets a top-level category be chosen in its own right (default: `true`). */\n allowParentSelection?: boolean;\n}\n\n/**\n * Category picker bound to {@link useFormContext}.\n *\n * The sibling {@link SelectField} flattens a category tree into one list, which\n * is the thing the hierarchical picker exists to stop. Use this wherever the\n * field's options nest.\n *\n * Form values are strings, so \"nothing chosen\" travels as `\"\"` — mapped to and\n * from the control's `null` here rather than making every caller do it.\n */\nexport function CategoryField({\n name,\n options,\n label,\n placeholder,\n allowParentSelection = true,\n}: CategoryFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n\n return (\n <CategorySelect\n mode=\"single\"\n fullWidth\n label={label}\n placeholder={placeholder}\n options={options}\n allowParentSelection={allowParentSelection}\n value={values[name] ? values[name] : null}\n error={error}\n onChange={(next) => setFieldValue(name, next ?? '')}\n dataTestId={`total-form-field-${name}`}\n />\n );\n}\n","'use client';\n\nimport React, { useCallback, useMemo, useRef } from 'react';\n\nimport { CepField as BaseCepField, type CepAddress } from '../../CepField';\nimport { useFormContext } from '../FormContext';\n\n/** The resolved address parts a form can receive. */\ntype CepPart = 'street' | 'neighborhood' | 'city' | 'state';\n\n/**\n * Which form field receives each resolved part. Omit a part to leave it alone —\n * a form with no bairro column simply doesn't map `neighborhood`.\n */\nexport type CepFills = Partial<Record<CepPart, string>>;\n\n/** Props for the `total-form` {@link CepField}. */\nexport interface CepFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Optional visible label. Defaults to `CEP`. */\n label?: string;\n /** Resolve a complete CEP. Omit for a masked input with no lookup. */\n lookup?: (cep: string) => Promise<CepAddress | null>;\n /** Maps each resolved address part onto a field in THIS form. */\n fills?: CepFills;\n /** Milliseconds to wait after the last keystroke before looking up. */\n debounceMs?: number;\n}\n\nconst PARTS: readonly CepPart[] = ['street', 'neighborhood', 'city', 'state'];\n\n/** Ownership-aware writes into sibling form fields. */\ninterface OwnedAutofill {\n /** Write one field, where `''` means \"no value for this part\". */\n write: (field: string, value: string) => void;\n /** Blank every field this hook still owns. */\n clearOwned: () => void;\n}\n\n/**\n * The honesty rule, in one place: this hook remembers exactly what it wrote,\n * writes only into fields that are empty or still hold its own value, and drops\n * the ownership record when it blanks one. That single invariant is what lets\n * autofill replace itself freely while never touching the user's typing.\n */\nfunction useOwnedAutofill(): OwnedAutofill {\n const { values, setFieldValue } = useFormContext();\n // What we last auto-filled per target field, so a later lookup may replace\n // its own value while a manual edit is preserved.\n const autoFilledRef = useRef<Record<string, string>>({});\n // Read values at call time without making them a dependency of the callback.\n const valuesRef = useRef(values);\n valuesRef.current = values;\n\n const write = useCallback(\n (field: string, value: string) => {\n const current = (valuesRef.current[field] ?? '').trim();\n const ours = autoFilledRef.current[field] === current;\n if (current !== '' && !ours) return;\n setFieldValue(field, value);\n if (value) {\n autoFilledRef.current[field] = value;\n } else {\n delete autoFilledRef.current[field];\n }\n },\n [setFieldValue],\n );\n\n const clearOwned = useCallback(() => {\n // Object.keys snapshots, so `write` may delete entries as we iterate.\n for (const field of Object.keys(autoFilledRef.current)) {\n write(field, '');\n }\n }, [write]);\n\n return useMemo(() => ({ write, clearOwned }), [write, clearOwned]);\n}\n\n/**\n * CEP field bound to {@link useFormContext}: masks and validates the CEP, then\n * writes the resolved logradouro/bairro/cidade/UF straight into the sibling\n * fields named by `fills`.\n *\n * It will not overwrite work the user has done. A target field is only written\n * when it is empty, or when it still holds the exact value THIS component put\n * there — so correcting the CEP re-fills the address, but a hand-edited street\n * survives the next lookup.\n *\n * @example\n * ```tsx\n * <Fields.CepField\n * name=\"postalCode\"\n * lookup={lookupCep}\n * fills={{ street: 'addressLine1', neighborhood: 'neighborhood', city: 'city', state: 'state' }}\n * />\n * ```\n */\nexport function CepField({\n name,\n label,\n lookup,\n fills,\n debounceMs,\n}: CepFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const { write, clearOwned } = useOwnedAutofill();\n\n const handleResolved = useCallback(\n (address: CepAddress) => {\n if (!fills) return;\n // Every mapped part is written on EVERY resolve, absent ones as ''. A\n // \"CEP geral\" (a small município's catch-all) carries no logradouro, so\n // skipping absent parts would leave the previous CEP's street here.\n for (const part of PARTS) {\n const field = fills[part];\n if (field) write(field, address[part] ?? '');\n }\n },\n [fills, write],\n );\n\n const handleChange = useCallback(\n (masked: string) => setFieldValue(name, masked),\n [name, setFieldValue],\n );\n\n return (\n <BaseCepField\n name={name}\n label={label}\n value={values[name] ?? ''}\n error={errors[name]}\n onChange={handleChange}\n onLookup={lookup}\n onResolved={handleResolved}\n onNotFound={clearOwned}\n debounceMs={debounceMs}\n dataTestId={`total-form-field-${name}`}\n />\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { Input } from '../../Input';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link TextField}. */\nexport interface TextFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Optional visible label rendered above the input. */\n label?: string;\n /** Native input type (e.g. `email`, `password`). Defaults to `text`. */\n type?: string;\n /** Placeholder shown when the field is empty. */\n placeholder?: string;\n}\n\n/**\n * Text input field bound to {@link useFormContext}. Composes the existing\n * `@12-apps/ui` `Form*` presentational primitives and the `Input` control,\n * reading its value/error from context and writing changes back through it.\n *\n * Renders its error via `total-form-field-<name>-message` and the input via\n * `total-form-field-<name>`.\n */\nexport function TextField({ name, label, type = 'text', placeholder }: TextFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Input\n id={name}\n name={name}\n type={type}\n placeholder={placeholder}\n value={values[name] ?? ''}\n error={Boolean(error)}\n data-testid={testId}\n onChange={(event) => setFieldValue(name, event.target.value)}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { Select } from '../../Select';\nimport type { SelectOption } from '../../Select';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link SelectField}. */\nexport interface SelectFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** Selectable options. */\n options: SelectOption[];\n /** Optional visible label rendered above the select. */\n label?: string;\n /** Placeholder shown when no option is selected. */\n placeholder?: string;\n}\n\n/**\n * Select field bound to {@link useFormContext}. Composes the existing\n * `@12-apps/ui` `Form*` primitives and the `Select` control, reading its\n * value/error from context and writing the chosen value back through it.\n *\n * Renders its error via `total-form-field-<name>-message` and the select via\n * `total-form-field-<name>`.\n */\nexport function SelectField({ name, options, label, placeholder }: SelectFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <Select\n options={options}\n placeholder={placeholder}\n value={values[name] ?? ''}\n error={Boolean(error)}\n data-testid={testId}\n onChange={(event) => setFieldValue(name, String(event.target.value))}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n","'use client';\n\nimport React from 'react';\n\nimport { FormControl, FormLabel, FormMessage } from '../../Form';\nimport { ToggleGroup } from '../../ToggleGroup';\nimport type { ToggleOption } from '../../ToggleGroup';\nimport { useFormContext } from '../FormContext';\n\n/** Props for the `total-form` {@link ToggleField}. */\nexport interface ToggleFieldProps {\n /** Field name; also the key into the form's values/errors. */\n name: string;\n /** The mutually-exclusive options (one is always selected). */\n options: ToggleOption[];\n /** Optional visible label rendered above the toggle. */\n label?: string;\n}\n\n/**\n * Exclusive toggle (segmented control) bound to {@link useFormContext} — a\n * label-height-matched alternative to {@link SelectField} for a short, always-set\n * enum. Composes the `Form*` primitives and the `ToggleGroup` control, reading\n * its value from context and writing the chosen value back through it. Ignores a\n * deselect (clicking the active segment) so a value is always kept.\n *\n * Renders its error via `total-form-field-<name>-message` and the toggle via\n * `total-form-field-<name>` (each segment at `total-form-field-<name>-item-<value>`).\n */\nexport function ToggleField({ name, options, label }: ToggleFieldProps): React.ReactElement {\n const { values, errors, setFieldValue } = useFormContext();\n const error = errors[name];\n const testId = `total-form-field-${name}`;\n\n return (\n <FormControl fullWidth>\n {label && (\n <FormLabel htmlFor={name} error={Boolean(error)}>\n {label}\n </FormLabel>\n )}\n <ToggleGroup\n variant=\"exclusive\"\n options={options}\n value={values[name] ?? ''}\n fullWidth\n dataTestId={testId}\n // Match the input field height so the toggle lines up in a form row.\n sx={{ '& .MuiToggleButtonGroup-grouped': { height: 56 } }}\n onChange={(_event, next) => {\n if (typeof next === 'string') setFieldValue(name, next);\n }}\n />\n {error && (\n <FormMessage error dataTestId={`${testId}-message`}>\n {error}\n </FormMessage>\n )}\n </FormControl>\n );\n}\n"],"mappings":"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","names":["jsx","jsx","CepField","jsx","useCallback","useMemo","jsx","useCallback","useMemo","CepField","jsx","jsx","jsxs","jsx","jsxs"]}
@@ -0,0 +1,16 @@
1
+ import {
2
+ MODAL_STACK_BASE_Z_INDEX,
3
+ MODAL_STACK_Z_INDEX_STEP,
4
+ STACKED_OVERLAY_CLEARED_LEVELS,
5
+ STACKED_OVERLAY_HEADROOM,
6
+ stackedOverlayZIndex
7
+ } from "../chunk-MQLM43E4.js";
8
+ import "../chunk-PZ5AY32C.js";
9
+ export {
10
+ MODAL_STACK_BASE_Z_INDEX,
11
+ MODAL_STACK_Z_INDEX_STEP,
12
+ STACKED_OVERLAY_CLEARED_LEVELS,
13
+ STACKED_OVERLAY_HEADROOM,
14
+ stackedOverlayZIndex
15
+ };
16
+ //# sourceMappingURL=layers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"modal-stack.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/StackedModal/modal-stack.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,KAAK,EAA6B,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAoB9F,eAAO,MAAM,aAAa,QAAO,sBAMhC,CAAC;AAyCF,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAiD1D,CAAC"}
1
+ {"version":3,"file":"modal-stack.d.ts","sourceRoot":"","sources":["../../../../../src/components/feedback/StackedModal/modal-stack.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAA6B,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AA0B9F,eAAO,MAAM,aAAa,QAAO,sBAMhC,CAAC;AAyCF,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAiD1D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"CategoryPanelSurface.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/CategorySelect/CategoryPanelSurface.tsx"],"names":[],"mappings":"AAOA,UAAU,YAAY;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAQ,GACT,EAAE,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA0BlC"}
1
+ {"version":3,"file":"CategoryPanelSurface.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/CategorySelect/CategoryPanelSurface.tsx"],"names":[],"mappings":"AAoBA,UAAU,YAAY;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAQ,GACT,EAAE,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA4BlC"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreatableSelect.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/CreatableSelect/CreatableSelect.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAyB,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AA2J3F;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,KAAK,EACL,WAAW,EACX,iBAAiD,EACjD,aAAa,EACb,QAAgB,EAChB,OAAe,EACf,SAAgB,EAChB,IAAW,EACX,UAA+B,GAChC,EAAE,oBAAoB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAuC1C"}
1
+ {"version":3,"file":"CreatableSelect.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/CreatableSelect/CreatableSelect.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAyB,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AA2J3F;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,KAAK,EACL,WAAW,EACX,iBAAiD,EACjD,aAAa,EACb,QAAgB,EAChB,OAAe,EACf,SAAgB,EAChB,IAAW,EACX,UAA+B,GAChC,EAAE,oBAAoB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyC1C"}
@@ -0,0 +1,63 @@
1
+ import type { Theme } from '@mui/material';
2
+ /**
3
+ * WHERE A PORTALLED OVERLAY SITS RELATIVE TO A STACK OF SHEETS.
4
+ *
5
+ * `StackedModal` does not put its panels on one layer. Each panel takes its own
6
+ * z-index as it joins the stack — {@link MODAL_STACK_BASE_Z_INDEX} for the
7
+ * first, another {@link MODAL_STACK_Z_INDEX_STEP} for every panel above it — so
8
+ * the top of a two-deep stack sits at 1310, not at `zIndex.modal`.
9
+ *
10
+ * Anything that portals out of a panel has to clear that. MUI's own defaults do
11
+ * not: a `Popover` (and therefore a `Menu`, and therefore a `Select`) renders at
12
+ * `zIndex.modal`, and a temporary `Drawer` a step lower still at
13
+ * `zIndex.drawer`. With exactly one sheet open the numbers tie and portal order
14
+ * settles it in the overlay's favour, which is why this looks fine right up
15
+ * until a second sheet opens over the first.
16
+ *
17
+ * ## What the failure looks like, because it is not "the panel is invisible"
18
+ *
19
+ * A `Popover` under a sheet is still a live modal. It takes focus into itself
20
+ * and holds it, and its backdrop is under the sheet too — so no click can reach
21
+ * the backdrop to dismiss it. The trigger flips its chevron, no panel appears,
22
+ * and from that point every keystroke aimed at the rest of the form is swallowed
23
+ * by an input nobody can see. One click on a field that looks inert leaves the
24
+ * whole form inert. Reported downstream as three unrelated blocks being
25
+ * "disabled" (12-57).
26
+ *
27
+ * ## Why this is a token and not a prop on each component
28
+ *
29
+ * The invariant belongs to the ladder, and the ladder is `StackedModal`'s. Left
30
+ * to individual components it is a rule each one has to remember, and the ones
31
+ * that did not remember are indistinguishable from the ones that did until a
32
+ * consumer stacks a sheet. `CategorySelect` never had it and `Autocomplete`
33
+ * answered the same question with `zIndex.tooltip` — three different heights for
34
+ * one question, in one package.
35
+ */
36
+ /** The z-index `StackedModal` gives the FIRST panel in a stack. */
37
+ export declare const MODAL_STACK_BASE_Z_INDEX = 1300;
38
+ /** How much higher each panel stacked on top of another one sits. */
39
+ export declare const MODAL_STACK_Z_INDEX_STEP = 10;
40
+ /**
41
+ * How many stacked panels a portalled overlay is guaranteed to clear.
42
+ *
43
+ * Ten rather than a number derived from the deepest stack anyone has built,
44
+ * because there is nothing to derive it from: a theme is resolved once, at paint
45
+ * time, and knows nothing about how many sheets happen to be open. So the
46
+ * headroom is a constant, and this is the depth it buys.
47
+ */
48
+ export declare const STACKED_OVERLAY_CLEARED_LEVELS = 10;
49
+ /** The constant added to `zIndex.modal` to clear that many panels — 100. */
50
+ export declare const STACKED_OVERLAY_HEADROOM: number;
51
+ /**
52
+ * The z-index for a surface that portals out of a sheet and must stay on top of
53
+ * it — a dropdown, a picker panel, a menu.
54
+ *
55
+ * Read from the theme rather than hardcoded so a host that has moved
56
+ * `zIndex.modal` keeps the same relationship instead of a stale absolute.
57
+ *
58
+ * ```tsx
59
+ * <Popover sx={{ zIndex: stackedOverlayZIndex }} … />
60
+ * ```
61
+ */
62
+ export declare const stackedOverlayZIndex: (theme: Theme) => number;
63
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../../src/tokens/layers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,qEAAqE;AACrE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,QACsB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,KAAK,KAAG,MACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@9.0.0",
6
6
  "type": "module",
@@ -41,6 +41,10 @@
41
41
  "types": "./dist/types/tokens/scales.d.ts",
42
42
  "default": "./dist/tokens.js"
43
43
  },
44
+ "./tokens/layers": {
45
+ "types": "./dist/types/tokens/layers.d.ts",
46
+ "default": "./dist/tokens/layers.js"
47
+ },
44
48
  "./mui/Box": {
45
49
  "types": "./dist/types/mui/Box.d.ts",
46
50
  "default": "./dist/mui/Box.js"
@@ -92,6 +92,32 @@ function NestedModals() {
92
92
  }
93
93
  ```
94
94
 
95
+ ### Overlays that portal out of a panel
96
+
97
+ Each panel takes its **own** z-index as it joins the stack — 1300 for the first,
98
+ another 10 for every panel above it. Anything that portals out of a panel has to
99
+ clear that ladder, and MUI's defaults do not: a `Popover` (so also a `Menu`, so
100
+ also a `Select`) renders at `zIndex.modal`, and a temporary `Drawer` a step lower
101
+ at `zIndex.drawer`.
102
+
103
+ With one sheet open the numbers tie and portal order settles it in the overlay's
104
+ favour, so this looks correct right until a second sheet opens over the first.
105
+ Then the overlay renders **under** the panel that opened it — and because a
106
+ `Popover` is a live modal, it still takes focus into itself and its backdrop is
107
+ unreachable, so every keystroke aimed at the surrounding form disappears into an
108
+ input nobody can see, with no way to dismiss it.
109
+
110
+ Take the height from the token rather than picking one:
111
+
112
+ ```tsx
113
+ import { stackedOverlayZIndex } from '@12-apps/ui/tokens/layers';
114
+
115
+ <Popover sx={{ zIndex: stackedOverlayZIndex }} … />
116
+ ```
117
+
118
+ It clears ten stacked panels. `CreatableSelect` and `CategorySelect` use it; a
119
+ new component that portals should too.
120
+
95
121
  ## Props
96
122
 
97
123
  | Prop | Type | Default | Description |
@@ -1,10 +1,17 @@
1
1
  import type { FC, ReactNode } from 'react';
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { MODAL_STACK_BASE_Z_INDEX, MODAL_STACK_Z_INDEX_STEP } from '../../../tokens/layers';
3
4
  import type { ModalInfo, ModalPanelRole, ModalStackContextValue } from './StackedModal.types';
4
5
 
5
- /** MUI Dialog default z-index; each panel in the stack sits 10 above the last. */
6
- const BASE_Z_INDEX = 1300;
7
- const Z_INDEX_STEP = 10;
6
+ /**
7
+ * MUI Dialog default z-index; each panel in the stack sits 10 above the last.
8
+ *
9
+ * Imported rather than declared here: anything that portals out of a panel has
10
+ * to clear this ladder, so the numbers are shared with the token that tells it
11
+ * how — see `tokens/layers`.
12
+ */
13
+ const BASE_Z_INDEX = MODAL_STACK_BASE_Z_INDEX;
14
+ const Z_INDEX_STEP = MODAL_STACK_Z_INDEX_STEP;
8
15
 
9
16
  const noop = (): void => {
10
17
  /** do nothing */
@@ -2,9 +2,22 @@
2
2
 
3
3
  import { Drawer, Popover } from '@mui/material';
4
4
 
5
+ import { stackedOverlayZIndex } from '../../../tokens/layers';
6
+
5
7
  /** Chrome-free surface props: the panel draws its own border and shadow. */
6
8
  const BARE_PAPER = { background: 'transparent', boxShadow: 'none' } as const;
7
9
 
10
+ /**
11
+ * Both surfaces clear the sheet ladder rather than taking MUI's default.
12
+ *
13
+ * A `Popover` defaults to `zIndex.modal` and a temporary `Drawer` to
14
+ * `zIndex.drawer`, one step lower still — and `StackedModal` gives its second
15
+ * panel 1310. So this panel opened UNDER the sheet that opened it, and, being a
16
+ * live modal with an unreachable backdrop, took the focus of every field around
17
+ * it with no way to dismiss it (12-57). See `tokens/layers`.
18
+ */
19
+ const ABOVE_SHEETS = { zIndex: stackedOverlayZIndex } as const;
20
+
8
21
  interface SurfaceProps {
9
22
  open: boolean;
10
23
  /** True below the sheet breakpoint — anchor to the bottom edge instead. */
@@ -34,6 +47,7 @@ export function CategoryPanelSurface({
34
47
  anchor="bottom"
35
48
  open={open}
36
49
  onClose={onClose}
50
+ sx={ABOVE_SHEETS}
37
51
  slotProps={{ paper: { sx: BARE_PAPER } }}
38
52
  >
39
53
  {children}
@@ -48,6 +62,7 @@ export function CategoryPanelSurface({
48
62
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
49
63
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
50
64
  marginThreshold={8}
65
+ sx={ABOVE_SHEETS}
51
66
  slotProps={{ paper: { sx: { ...BARE_PAPER, marginTop: '6px', overflow: 'visible' } } }}
52
67
  >
53
68
  {children}
@@ -7,6 +7,7 @@ import { CircularProgress } from '@mui/material';
7
7
  import { TextField } from '@mui/material';
8
8
  import { useId, useMemo } from 'react';
9
9
 
10
+ import { stackedOverlayZIndex } from '../../../tokens/layers';
10
11
  import { FormControl, FormLabel, FormMessage } from '../Form';
11
12
  import type { CreatableSelectOption, CreatableSelectProps } from './CreatableSelect.types';
12
13
  import type { SizeValue } from '../../../tokens/scales';
@@ -216,8 +217,10 @@ export function CreatableSelect({
216
217
  noOptionsText={noOptionsText}
217
218
  // Lift the dropdown above stacked modals: the default popup z-index is
218
219
  // `modal` (1300), but a stacked sheet at depth ≥2 sits at 1300 + depth*10,
219
- // which would otherwise cover the options. +100 clears ~10 stack levels.
220
- slotProps={{ popper: { sx: { zIndex: (theme) => theme.zIndex.modal + 100 } } }}
220
+ // which would otherwise cover the options. The height is the token's
221
+ // now — this component used to be the only place the rule was written
222
+ // down, which is how `CategorySelect` came to be missing it (12-57).
223
+ slotProps={{ popper: { sx: { zIndex: stackedOverlayZIndex } } }}
221
224
  getOptionLabel={(option) => (typeof option === 'string' ? option : option.label)}
222
225
  isOptionEqualToValue={(option, current) => option.value === current.value}
223
226
  renderOption={renderIndentedOption}
@@ -0,0 +1,70 @@
1
+ import type { Theme } from '@mui/material';
2
+
3
+ /**
4
+ * WHERE A PORTALLED OVERLAY SITS RELATIVE TO A STACK OF SHEETS.
5
+ *
6
+ * `StackedModal` does not put its panels on one layer. Each panel takes its own
7
+ * z-index as it joins the stack — {@link MODAL_STACK_BASE_Z_INDEX} for the
8
+ * first, another {@link MODAL_STACK_Z_INDEX_STEP} for every panel above it — so
9
+ * the top of a two-deep stack sits at 1310, not at `zIndex.modal`.
10
+ *
11
+ * Anything that portals out of a panel has to clear that. MUI's own defaults do
12
+ * not: a `Popover` (and therefore a `Menu`, and therefore a `Select`) renders at
13
+ * `zIndex.modal`, and a temporary `Drawer` a step lower still at
14
+ * `zIndex.drawer`. With exactly one sheet open the numbers tie and portal order
15
+ * settles it in the overlay's favour, which is why this looks fine right up
16
+ * until a second sheet opens over the first.
17
+ *
18
+ * ## What the failure looks like, because it is not "the panel is invisible"
19
+ *
20
+ * A `Popover` under a sheet is still a live modal. It takes focus into itself
21
+ * and holds it, and its backdrop is under the sheet too — so no click can reach
22
+ * the backdrop to dismiss it. The trigger flips its chevron, no panel appears,
23
+ * and from that point every keystroke aimed at the rest of the form is swallowed
24
+ * by an input nobody can see. One click on a field that looks inert leaves the
25
+ * whole form inert. Reported downstream as three unrelated blocks being
26
+ * "disabled" (12-57).
27
+ *
28
+ * ## Why this is a token and not a prop on each component
29
+ *
30
+ * The invariant belongs to the ladder, and the ladder is `StackedModal`'s. Left
31
+ * to individual components it is a rule each one has to remember, and the ones
32
+ * that did not remember are indistinguishable from the ones that did until a
33
+ * consumer stacks a sheet. `CategorySelect` never had it and `Autocomplete`
34
+ * answered the same question with `zIndex.tooltip` — three different heights for
35
+ * one question, in one package.
36
+ */
37
+
38
+ /** The z-index `StackedModal` gives the FIRST panel in a stack. */
39
+ export const MODAL_STACK_BASE_Z_INDEX = 1300;
40
+
41
+ /** How much higher each panel stacked on top of another one sits. */
42
+ export const MODAL_STACK_Z_INDEX_STEP = 10;
43
+
44
+ /**
45
+ * How many stacked panels a portalled overlay is guaranteed to clear.
46
+ *
47
+ * Ten rather than a number derived from the deepest stack anyone has built,
48
+ * because there is nothing to derive it from: a theme is resolved once, at paint
49
+ * time, and knows nothing about how many sheets happen to be open. So the
50
+ * headroom is a constant, and this is the depth it buys.
51
+ */
52
+ export const STACKED_OVERLAY_CLEARED_LEVELS = 10;
53
+
54
+ /** The constant added to `zIndex.modal` to clear that many panels — 100. */
55
+ export const STACKED_OVERLAY_HEADROOM =
56
+ MODAL_STACK_Z_INDEX_STEP * STACKED_OVERLAY_CLEARED_LEVELS;
57
+
58
+ /**
59
+ * The z-index for a surface that portals out of a sheet and must stay on top of
60
+ * it — a dropdown, a picker panel, a menu.
61
+ *
62
+ * Read from the theme rather than hardcoded so a host that has moved
63
+ * `zIndex.modal` keeps the same relationship instead of a stale absolute.
64
+ *
65
+ * ```tsx
66
+ * <Popover sx={{ zIndex: stackedOverlayZIndex }} … />
67
+ * ```
68
+ */
69
+ export const stackedOverlayZIndex = (theme: Theme): number =>
70
+ theme.zIndex.modal + STACKED_OVERLAY_HEADROOM;