@12-apps/ui 6.2.0 → 6.3.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/dist/{chunk-35VGGLOC.js → chunk-CEKPLUQV.js} +2 -2
- package/dist/{chunk-35VGGLOC.js.map → chunk-CEKPLUQV.js.map} +1 -1
- package/dist/{chunk-62XQWGXU.js → chunk-MXPXKORR.js} +19 -17
- package/dist/chunk-MXPXKORR.js.map +1 -0
- package/dist/{chunk-7XLEWBKL.js → chunk-N554AFDB.js} +7 -10
- package/dist/chunk-N554AFDB.js.map +1 -0
- package/dist/{chunk-JDO7W4D4.js → chunk-R5ZQZSEQ.js} +2 -20
- package/dist/{chunk-JDO7W4D4.js.map → chunk-R5ZQZSEQ.js.map} +1 -1
- package/dist/{chunk-R4TYXKDD.js → chunk-TPQRIPCN.js} +52 -48
- package/dist/chunk-TPQRIPCN.js.map +1 -0
- package/dist/copy.js +1 -0
- package/dist/copy.js.map +1 -0
- package/dist/data-display/CardKit.js +15 -4
- package/dist/data-display/CardKit.js.map +1 -1
- package/dist/data-display/DataViews.js +664 -616
- package/dist/data-display/DataViews.js.map +1 -1
- package/dist/data-display/SectionOnboarding.js +3 -3
- package/dist/data-display/SectionOnboarding.js.map +1 -1
- package/dist/feedback/ConfirmAction.js +3 -5
- package/dist/form/CategorySelect.js +1 -1
- package/dist/form/CepField.js +1 -1
- package/dist/form/total-form.js +8 -4
- package/dist/form/total-form.js.map +1 -1
- package/dist/layout/ContentToolbar.js +1 -1
- package/dist/layout/TableFilter.js +1 -1
- package/dist/pt-BR.js +222 -0
- package/dist/pt-BR.js.map +1 -0
- package/dist/types/components/data-display/CardKit/use-remove-confirm.d.ts +4 -1
- package/dist/types/components/data-display/CardKit/use-remove-confirm.d.ts.map +1 -1
- package/dist/types/components/data-display/CardKit/use-row-confirm.d.ts +5 -2
- package/dist/types/components/data-display/CardKit/use-row-confirm.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/DataViewsBoard.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/DeleteViewDialog.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/ManageViewsDialog.d.ts +4 -1
- package/dist/types/components/data-display/DataViews/ManageViewsDialog.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/SaveViewModal.d.ts +0 -5
- package/dist/types/components/data-display/DataViews/SaveViewModal.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/base-card.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/base-list-card-gutters.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-bar-controls.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-category-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-columns-tab.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-copy-context.d.ts +17 -0
- package/dist/types/components/data-display/DataViews/data-views-copy-context.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +205 -0
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/data-views-display-panel.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-display-tabs.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-export.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-filter-panel.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-helpers.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-filters.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-presets.d.ts +3 -2
- package/dist/types/components/data-display/DataViews/data-views-range-presets.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-scopes.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-search.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-select-all-strip.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-table-parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-view-nav.d.ts +1 -1
- package/dist/types/components/data-display/DataViews/data-views-view-nav.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/index.d.ts +2 -0
- package/dist/types/components/data-display/DataViews/index.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/save-view-preview.d.ts +10 -0
- package/dist/types/components/data-display/DataViews/save-view-preview.d.ts.map +1 -0
- package/dist/types/components/data-display/DataViews/use-saved-views-controller.d.ts.map +1 -1
- package/dist/types/components/data-display/SectionOnboarding/SectionOnboarding.types.d.ts +6 -5
- package/dist/types/components/data-display/SectionOnboarding/SectionOnboarding.types.d.ts.map +1 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmAction.hooks.d.ts +1 -3
- package/dist/types/components/feedback/ConfirmAction/ConfirmAction.hooks.d.ts.map +1 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmAction.parts.d.ts +4 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmAction.parts.d.ts.map +1 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmAction.types.d.ts +9 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmAction.types.d.ts.map +1 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmButton.d.ts +2 -1
- package/dist/types/components/feedback/ConfirmAction/ConfirmButton.d.ts.map +1 -1
- package/dist/types/components/feedback/ConfirmAction/index.d.ts +2 -2
- package/dist/types/components/feedback/ConfirmAction/index.d.ts.map +1 -1
- package/dist/types/components/feedback/ConfirmAction/with-confirmation.d.ts +5 -1
- package/dist/types/components/feedback/ConfirmAction/with-confirmation.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryPanel.d.ts +4 -1
- package/dist/types/components/form/CategorySelect/CategoryPanel.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryPanelFoot.d.ts +5 -2
- package/dist/types/components/form/CategorySelect/CategoryPanelFoot.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryPanelHead.d.ts +5 -2
- package/dist/types/components/form/CategorySelect/CategoryPanelHead.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts +3 -0
- package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategorySelect.types.d.ts +5 -0
- package/dist/types/components/form/CategorySelect/CategorySelect.types.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryTrigger.d.ts +4 -1
- package/dist/types/components/form/CategorySelect/CategoryTrigger.d.ts.map +1 -1
- package/dist/types/components/form/CepField/CepField.d.ts +1 -1
- package/dist/types/components/form/CepField/CepField.d.ts.map +1 -1
- package/dist/types/components/form/CepField/CepField.types.d.ts +3 -0
- package/dist/types/components/form/CepField/CepField.types.d.ts.map +1 -1
- package/dist/types/components/form/total-form/Fields/CategoryField.d.ts +4 -1
- package/dist/types/components/form/total-form/Fields/CategoryField.d.ts.map +1 -1
- package/dist/types/components/form/total-form/Fields/CepField.d.ts +4 -1
- package/dist/types/components/form/total-form/Fields/CepField.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.types.d.ts +6 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.types.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/SavedViewsMenu.d.ts.map +1 -1
- package/dist/types/copy.d.ts +99 -0
- package/dist/types/copy.d.ts.map +1 -0
- package/dist/types/pt-BR.d.ts +28 -0
- package/dist/types/pt-BR.d.ts.map +1 -0
- package/package.json +10 -2
- package/src/components/data-display/CardKit/use-remove-confirm.tsx +5 -0
- package/src/components/data-display/CardKit/use-row-confirm.tsx +7 -2
- package/src/components/data-display/DataViews/DataViewsBoard.tsx +7 -4
- package/src/components/data-display/DataViews/DeleteViewDialog.tsx +6 -4
- package/src/components/data-display/DataViews/ManageViewsDialog.tsx +26 -10
- package/src/components/data-display/DataViews/SaveViewModal.tsx +56 -90
- package/src/components/data-display/DataViews/base-card.tsx +3 -1
- package/src/components/data-display/DataViews/base-list-card-gutters.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-bar-controls.tsx +8 -5
- package/src/components/data-display/DataViews/data-views-category-pill.tsx +6 -3
- package/src/components/data-display/DataViews/data-views-columns-tab.tsx +5 -3
- package/src/components/data-display/DataViews/data-views-copy-context.tsx +43 -0
- package/src/components/data-display/DataViews/data-views-copy.ts +214 -0
- package/src/components/data-display/DataViews/data-views-display-panel.tsx +15 -9
- package/src/components/data-display/DataViews/data-views-display-tabs.tsx +19 -29
- package/src/components/data-display/DataViews/data-views-export.tsx +9 -10
- package/src/components/data-display/DataViews/data-views-filter-panel.tsx +9 -5
- package/src/components/data-display/DataViews/data-views-grid-helpers.tsx +5 -2
- package/src/components/data-display/DataViews/data-views-more-fields.tsx +5 -3
- package/src/components/data-display/DataViews/data-views-more-filters.tsx +6 -3
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +9 -5
- package/src/components/data-display/DataViews/data-views-range-presets.ts +7 -8
- package/src/components/data-display/DataViews/data-views-scopes.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-search.tsx +7 -4
- package/src/components/data-display/DataViews/data-views-select-all-strip.tsx +5 -3
- package/src/components/data-display/DataViews/data-views-table-parts.tsx +7 -2
- package/src/components/data-display/DataViews/data-views-view-nav.tsx +22 -16
- package/src/components/data-display/DataViews/index.ts +20 -0
- package/src/components/data-display/DataViews/save-view-preview.tsx +78 -0
- package/src/components/data-display/DataViews/use-saved-views-controller.ts +3 -1
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.tsx +3 -3
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.types.ts +8 -7
- package/src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts +2 -2
- package/src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx +12 -5
- package/src/components/feedback/ConfirmAction/ConfirmAction.types.ts +9 -1
- package/src/components/feedback/ConfirmAction/ConfirmButton.tsx +12 -4
- package/src/components/feedback/ConfirmAction/index.ts +2 -2
- package/src/components/feedback/ConfirmAction/with-confirmation.tsx +4 -3
- package/src/components/form/CategorySelect/CategoryPanel.tsx +18 -6
- package/src/components/form/CategorySelect/CategoryPanelFoot.tsx +10 -5
- package/src/components/form/CategorySelect/CategoryPanelHead.tsx +8 -3
- package/src/components/form/CategorySelect/CategoryPanelList.tsx +17 -10
- package/src/components/form/CategorySelect/CategorySelect.tsx +3 -3
- package/src/components/form/CategorySelect/CategorySelect.types.ts +5 -0
- package/src/components/form/CategorySelect/CategoryTrigger.tsx +5 -1
- package/src/components/form/CepField/CepField.tsx +6 -9
- package/src/components/form/CepField/CepField.types.ts +3 -0
- package/src/components/form/total-form/Fields/CategoryField.tsx +5 -0
- package/src/components/form/total-form/Fields/CepField.tsx +5 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +6 -1
- package/src/components/layout/ContentToolbar/SavedViewsMenu.tsx +1 -21
- package/src/components/layout/TableFilter/TableFilter.tsx +1 -1
- package/src/copy.ts +103 -0
- package/src/pt-BR.ts +247 -0
- package/dist/chunk-62XQWGXU.js.map +0 -1
- package/dist/chunk-7XLEWBKL.js.map +0 -1
- package/dist/chunk-R4TYXKDD.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CategorySelectCopy } from "../../../copy";
|
|
1
2
|
import type { SizeValue } from '../../../tokens/scales';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -84,6 +85,8 @@ export interface CategorySelectBaseProps {
|
|
|
84
85
|
* not reload on every tick.
|
|
85
86
|
*/
|
|
86
87
|
export interface CategoryMultiSelectProps extends CategorySelectBaseProps {
|
|
88
|
+
/** The words this control renders. REQUIRED — no default copy. */
|
|
89
|
+
copy: CategorySelectCopy;
|
|
87
90
|
mode?: 'multi';
|
|
88
91
|
/** The APPLIED selection — subcategory ids. */
|
|
89
92
|
value: string[];
|
|
@@ -93,6 +96,8 @@ export interface CategoryMultiSelectProps extends CategorySelectBaseProps {
|
|
|
93
96
|
|
|
94
97
|
/** Single-select mode: the "move to…" picker. Choosing a row commits at once. */
|
|
95
98
|
export interface CategorySingleSelectProps extends CategorySelectBaseProps {
|
|
99
|
+
/** The words this control renders. REQUIRED — no default copy. */
|
|
100
|
+
copy: CategorySelectCopy;
|
|
96
101
|
mode: 'single';
|
|
97
102
|
/** The chosen category or subcategory id, or `null`. */
|
|
98
103
|
value: string | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { CategorySelectCopy } from "../../../copy";
|
|
3
4
|
import { Box } from '@mui/material';
|
|
4
5
|
|
|
5
6
|
import { CaretGlyph } from './CategoryIcons';
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
} from './CategorySelect.styles';
|
|
12
13
|
|
|
13
14
|
interface CategoryTriggerProps {
|
|
15
|
+
/** The words this renders. REQUIRED — this package ships no default copy. */
|
|
16
|
+
copy: CategorySelectCopy;
|
|
14
17
|
/** Text shown when nothing is selected. */
|
|
15
18
|
placeholder: string;
|
|
16
19
|
/** Resolved label when there IS a selection (single mode shows "Pai › Filha"). */
|
|
@@ -46,6 +49,7 @@ export function CategoryTrigger({
|
|
|
46
49
|
onClear,
|
|
47
50
|
triggerRef,
|
|
48
51
|
dataTestId,
|
|
52
|
+
copy,
|
|
49
53
|
}: CategoryTriggerProps): React.JSX.Element {
|
|
50
54
|
const hasSelection = Boolean(selectionLabel) || (count ?? 0) > 0;
|
|
51
55
|
return (
|
|
@@ -86,7 +90,7 @@ export function CategoryTrigger({
|
|
|
86
90
|
component="span"
|
|
87
91
|
role="button"
|
|
88
92
|
tabIndex={0}
|
|
89
|
-
aria-label=
|
|
93
|
+
aria-label={copy.clearSelection}
|
|
90
94
|
data-testid={`${dataTestId}-clear-trigger`}
|
|
91
95
|
sx={triggerClearSx}
|
|
92
96
|
onClick={(event: React.MouseEvent) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { CepFieldCopy } from "../../../copy";
|
|
3
4
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
5
|
|
|
5
6
|
import { cepDigits, formatCep, isValidCep } from '@12-apps/forms-core';
|
|
@@ -8,13 +9,6 @@ import { FormControl, FormLabel, FormMessage } from '../Form';
|
|
|
8
9
|
import { Input } from '../Input';
|
|
9
10
|
import type { CepAddress, CepFieldProps, CepLookupStatus } from './CepField.types';
|
|
10
11
|
|
|
11
|
-
/** What the user is told for each non-idle outcome. */
|
|
12
|
-
const STATUS_TEXT: Record<Exclude<CepLookupStatus, 'idle'>, string> = {
|
|
13
|
-
loading: 'Buscando endereço…',
|
|
14
|
-
found: 'Endereço preenchido pelo CEP.',
|
|
15
|
-
notfound: 'CEP não encontrado — preencha o endereço manualmente.',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
12
|
/** Everything {@link useCepLookup} needs, so the hook signature stays flat. */
|
|
19
13
|
interface LookupOptions {
|
|
20
14
|
digits: string;
|
|
@@ -103,17 +97,19 @@ function CepStatus({
|
|
|
103
97
|
status,
|
|
104
98
|
testId,
|
|
105
99
|
suppressed,
|
|
100
|
+
copy,
|
|
106
101
|
}: {
|
|
107
102
|
status: CepLookupStatus;
|
|
108
103
|
testId: string;
|
|
109
104
|
suppressed: boolean;
|
|
105
|
+
copy: CepFieldCopy;
|
|
110
106
|
}): React.ReactElement {
|
|
111
107
|
const visible = !suppressed && status !== 'idle';
|
|
112
108
|
return (
|
|
113
109
|
<span id={`${testId}-status`} role="status" aria-live="polite">
|
|
114
110
|
{visible && (
|
|
115
111
|
<FormMessage error={status === 'notfound'} dataTestId={`${testId}-${status}`}>
|
|
116
|
-
{
|
|
112
|
+
{copy[status as Exclude<CepLookupStatus, 'idle'>]}
|
|
117
113
|
</FormMessage>
|
|
118
114
|
)}
|
|
119
115
|
</span>
|
|
@@ -157,6 +153,7 @@ export function CepField({
|
|
|
157
153
|
debounceMs = 500,
|
|
158
154
|
name = 'postalCode',
|
|
159
155
|
dataTestId,
|
|
156
|
+
copy,
|
|
160
157
|
}: CepFieldProps): React.ReactElement {
|
|
161
158
|
const testId = dataTestId ?? `cep-field-${name}`;
|
|
162
159
|
|
|
@@ -203,7 +200,7 @@ export function CepField({
|
|
|
203
200
|
{error}
|
|
204
201
|
</FormMessage>
|
|
205
202
|
)}
|
|
206
|
-
<CepStatus status={status} testId={testId} suppressed={Boolean(error)} />
|
|
203
|
+
<CepStatus status={status} testId={testId} suppressed={Boolean(error)} copy={copy} />
|
|
207
204
|
</FormControl>
|
|
208
205
|
);
|
|
209
206
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CepFieldCopy } from "../../../copy";
|
|
1
2
|
/**
|
|
2
3
|
* Types for {@link CepField} — the reusable CEP auto-completer.
|
|
3
4
|
*/
|
|
@@ -29,6 +30,8 @@ export interface CepAddress {
|
|
|
29
30
|
export type CepLookupStatus = 'idle' | 'loading' | 'found' | 'notfound';
|
|
30
31
|
|
|
31
32
|
export interface CepFieldProps {
|
|
33
|
+
/** The lookup status wording. REQUIRED — no default copy. */
|
|
34
|
+
copy: CepFieldCopy;
|
|
32
35
|
/** Current field value. Masked (`01310-100`) or bare — the field re-masks. */
|
|
33
36
|
value: string;
|
|
34
37
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { CategorySelectCopy } from "../../../../copy";
|
|
3
4
|
import React from 'react';
|
|
4
5
|
|
|
5
6
|
import { CategorySelect } from '../../CategorySelect';
|
|
@@ -8,6 +9,8 @@ import { useFormContext } from '../FormContext';
|
|
|
8
9
|
|
|
9
10
|
/** Props for the `total-form` {@link CategoryField}. */
|
|
10
11
|
export interface CategoryFieldProps {
|
|
12
|
+
/** The picker\'s words. REQUIRED — this package ships no default copy. */
|
|
13
|
+
copy: CategorySelectCopy;
|
|
11
14
|
/** Field name; also the key into the form's values/errors. */
|
|
12
15
|
name: string;
|
|
13
16
|
/** The categories to choose from, flat, each carrying its `parentId`. */
|
|
@@ -36,12 +39,14 @@ export function CategoryField({
|
|
|
36
39
|
label,
|
|
37
40
|
placeholder,
|
|
38
41
|
allowParentSelection = true,
|
|
42
|
+
copy,
|
|
39
43
|
}: CategoryFieldProps): React.ReactElement {
|
|
40
44
|
const { values, errors, setFieldValue } = useFormContext();
|
|
41
45
|
const error = errors[name];
|
|
42
46
|
|
|
43
47
|
return (
|
|
44
48
|
<CategorySelect
|
|
49
|
+
copy={copy}
|
|
45
50
|
mode="single"
|
|
46
51
|
fullWidth
|
|
47
52
|
label={label}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { CepFieldCopy } from "../../../../copy";
|
|
3
4
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
4
5
|
|
|
5
6
|
import { CepField as BaseCepField, type CepAddress } from '../../CepField';
|
|
@@ -16,6 +17,8 @@ export type CepFills = Partial<Record<CepPart, string>>;
|
|
|
16
17
|
|
|
17
18
|
/** Props for the `total-form` {@link CepField}. */
|
|
18
19
|
export interface CepFieldProps {
|
|
20
|
+
/** The lookup status wording. REQUIRED — no default copy. */
|
|
21
|
+
copy: CepFieldCopy;
|
|
19
22
|
/** Field name; also the key into the form's values/errors. */
|
|
20
23
|
name: string;
|
|
21
24
|
/** Optional visible label. Defaults to `CEP`. */
|
|
@@ -103,6 +106,7 @@ export function CepField({
|
|
|
103
106
|
lookup,
|
|
104
107
|
fills,
|
|
105
108
|
debounceMs,
|
|
109
|
+
copy,
|
|
106
110
|
}: CepFieldProps): React.ReactElement {
|
|
107
111
|
const { values, errors, setFieldValue } = useFormContext();
|
|
108
112
|
const { write, clearOwned } = useOwnedAutofill();
|
|
@@ -128,6 +132,7 @@ export function CepField({
|
|
|
128
132
|
|
|
129
133
|
return (
|
|
130
134
|
<BaseCepField
|
|
135
|
+
copy={copy}
|
|
131
136
|
name={name}
|
|
132
137
|
label={label}
|
|
133
138
|
value={values[name] ?? ''}
|
|
@@ -258,6 +258,11 @@ export interface SavedViewsMenuProps<V extends SavedViewLike = SavedViewLike> {
|
|
|
258
258
|
onToggleShare: (view: V) => void;
|
|
259
259
|
onManageAll: () => void;
|
|
260
260
|
/** Override any of the default (pt-BR) strings. */
|
|
261
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Every word this menu renders. REQUIRED since FUT-760: it used to be
|
|
263
|
+
* `Partial<…>` over a pt-BR default, so a host that passed nothing shipped
|
|
264
|
+
* one product's Portuguese. `PT_BR_SAVED_VIEWS_LABELS` is that exact set.
|
|
265
|
+
*/
|
|
266
|
+
labels: SavedViewsLabels;
|
|
262
267
|
testIdPrefix?: string;
|
|
263
268
|
}
|
|
@@ -13,26 +13,6 @@ import type {
|
|
|
13
13
|
SavedViewsMenuProps,
|
|
14
14
|
} from './ContentToolbar.types';
|
|
15
15
|
|
|
16
|
-
/** Default (pt-BR) strings; override any via the `labels` prop. */
|
|
17
|
-
const DEFAULT_LABELS: SavedViewsLabels = {
|
|
18
|
-
trigger: 'Visões',
|
|
19
|
-
mainView: 'Visão principal',
|
|
20
|
-
pinned: 'Fixadas',
|
|
21
|
-
recent: 'Recentes',
|
|
22
|
-
empty: 'Nenhuma visão salva ainda.',
|
|
23
|
-
saveCurrent: '+ Salvar visão atual',
|
|
24
|
-
manageAll: 'Gerenciar visões',
|
|
25
|
-
apply: 'Aplicar',
|
|
26
|
-
setDefault: 'Definir como padrão',
|
|
27
|
-
pin: 'Fixar na barra lateral',
|
|
28
|
-
unpin: 'Desafixar',
|
|
29
|
-
share: 'Compartilhar com a equipe',
|
|
30
|
-
unshare: 'Tornar privada',
|
|
31
|
-
edit: 'Editar',
|
|
32
|
-
remove: 'Excluir',
|
|
33
|
-
defaultTag: 'Padrão',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
16
|
/** How many non-pinned views show under "Recentes". */
|
|
37
17
|
const RECENT_LIMIT = 5;
|
|
38
18
|
|
|
@@ -280,7 +260,7 @@ export function SavedViewsMenu<V extends SavedViewLike = SavedViewLike>({
|
|
|
280
260
|
action();
|
|
281
261
|
close();
|
|
282
262
|
};
|
|
283
|
-
const labels =
|
|
263
|
+
const labels = labelOverrides;
|
|
284
264
|
const pinned = views.filter((view) => view.pinned);
|
|
285
265
|
const recent = views.filter((view) => !view.pinned).slice(0, RECENT_LIMIT);
|
|
286
266
|
|
|
@@ -328,7 +328,7 @@ const TableFilterRangeField = ({
|
|
|
328
328
|
step,
|
|
329
329
|
minPlaceholder = 'min',
|
|
330
330
|
maxPlaceholder = 'max',
|
|
331
|
-
invalidLabel
|
|
331
|
+
invalidLabel,
|
|
332
332
|
testId,
|
|
333
333
|
}: TableFilterRangeFieldProps): React.JSX.Element => {
|
|
334
334
|
const invalid = isRangeInverted(value);
|
package/src/copy.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words the standalone components render, as REQUIRED host config
|
|
3
|
+
* (FUT-760).
|
|
4
|
+
*
|
|
5
|
+
* `@12-apps/ui` is the package EVERY host renders, so copy compiled in here
|
|
6
|
+
* reached every adopter of every other package too — the same leak as anywhere
|
|
7
|
+
* else, at the widest possible blast radius. Several of these components made
|
|
8
|
+
* it worse by taking the words as OPTIONAL props with pt-BR defaults, which
|
|
9
|
+
* reads as configurable right up until nobody configures it.
|
|
10
|
+
*
|
|
11
|
+
* The DataViews family has its own object (`DataViewsCopy`) threaded through a
|
|
12
|
+
* context, because nineteen files are one mounted surface. These six are
|
|
13
|
+
* mounted independently, so each takes what it renders.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface SectionOnboardingCopy {
|
|
17
|
+
/** The button that begins the guided section. */
|
|
18
|
+
start: string;
|
|
19
|
+
/** The reveal toggle in the configured state, and its collapsed twin. */
|
|
20
|
+
edit: string;
|
|
21
|
+
collapse: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ConfirmActionCopy {
|
|
25
|
+
/** The dismiss button every confirmation shares. */
|
|
26
|
+
cancel: string;
|
|
27
|
+
/** Shown when the action fails and the caller supplied no reason. */
|
|
28
|
+
defaultError: string;
|
|
29
|
+
/**
|
|
30
|
+
* The fallback body when the caller names no entity.
|
|
31
|
+
*
|
|
32
|
+
* A caller that DOES name one gets the host's own sentence — this package
|
|
33
|
+
* never guesses a noun for someone else's rows.
|
|
34
|
+
*/
|
|
35
|
+
irreversible: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CategorySelectCopy {
|
|
39
|
+
/**
|
|
40
|
+
* What categories are FOR, in the host's own terms.
|
|
41
|
+
*
|
|
42
|
+
* This used to read "Categorias organizam o cardápio e os filtros da loja" —
|
|
43
|
+
* a restaurant's menu, hard-coded into a generic picker. A host selling
|
|
44
|
+
* insurance policies got told about its cardápio.
|
|
45
|
+
*/
|
|
46
|
+
purpose: string;
|
|
47
|
+
clearSelection: string;
|
|
48
|
+
/** The empty state, when the host has no categories at all. */
|
|
49
|
+
emptyTitle: string;
|
|
50
|
+
createCategory: string;
|
|
51
|
+
/**
|
|
52
|
+
* The search found nothing.
|
|
53
|
+
*
|
|
54
|
+
* Two of the four interpolate the term typed, so they are functions rather
|
|
55
|
+
* than strings with a placeholder to substitute: where the term sits in the
|
|
56
|
+
* sentence is the translator's call, not ours.
|
|
57
|
+
*/
|
|
58
|
+
noResults: {
|
|
59
|
+
title: (query: string) => string;
|
|
60
|
+
hint: string;
|
|
61
|
+
clearSearch: string;
|
|
62
|
+
create: (query: string) => string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The panel's two footers.
|
|
66
|
+
*
|
|
67
|
+
* `selectedCount` is a function because Portuguese agrees the participle with
|
|
68
|
+
* the count and English does not — the shape has to let a translator decide
|
|
69
|
+
* the whole sentence, not fill a slot in ours.
|
|
70
|
+
*/
|
|
71
|
+
/** The search box above the list, and the chips beneath it. */
|
|
72
|
+
search: {
|
|
73
|
+
/** Two placeholders: a single-select picker offers no subcategories. */
|
|
74
|
+
placeholderSingle: string;
|
|
75
|
+
placeholderMulti: string;
|
|
76
|
+
clear: string;
|
|
77
|
+
removeChip(label: string): string;
|
|
78
|
+
/** The tray above the list, counting what is already picked. */
|
|
79
|
+
pinnedLabel(count: number): string;
|
|
80
|
+
};
|
|
81
|
+
footer: {
|
|
82
|
+
selectedCount(count: number): string;
|
|
83
|
+
clear: string;
|
|
84
|
+
/** The primary button, which reads `close` while the draft is unchanged. */
|
|
85
|
+
apply: string;
|
|
86
|
+
close: string;
|
|
87
|
+
/** The single-select footer: what to do, and the way out. */
|
|
88
|
+
singleHint: string;
|
|
89
|
+
cancel: string;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface CepFieldCopy {
|
|
94
|
+
loading: string;
|
|
95
|
+
found: string;
|
|
96
|
+
/** Keyed to match `CepLookupStatus` exactly, so the lookup can index it. */
|
|
97
|
+
notfound: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface TableFilterCopy {
|
|
101
|
+
/** A numeric range whose maximum is below its minimum. */
|
|
102
|
+
invalidRange: string;
|
|
103
|
+
}
|
package/src/pt-BR.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { DataViewsCopy } from "./components/data-display/DataViews/data-views-copy";
|
|
2
|
+
import type { SavedViewsLabels } from "./components/layout/ContentToolbar/ContentToolbar.types";
|
|
3
|
+
import type {
|
|
4
|
+
CategorySelectCopy,
|
|
5
|
+
CepFieldCopy,
|
|
6
|
+
ConfirmActionCopy,
|
|
7
|
+
SectionOnboardingCopy,
|
|
8
|
+
TableFilterCopy,
|
|
9
|
+
} from "./copy";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The pt-BR packs — NAMED constants a host passes by hand, never defaults.
|
|
13
|
+
*
|
|
14
|
+
* The filename is what exempts this file from the copy-portability gate:
|
|
15
|
+
* Portuguese may ship, it may not be silent. Every sentence is VERBATIM what
|
|
16
|
+
* the components used to render, so a host adopting these sees no change on
|
|
17
|
+
* screen — what changes is that the words are chosen in a diff.
|
|
18
|
+
*/
|
|
19
|
+
export const PT_BR_SECTION_ONBOARDING_COPY: SectionOnboardingCopy = {
|
|
20
|
+
edit: "Editar",
|
|
21
|
+
collapse: "Ocultar",
|
|
22
|
+
start: "Começar",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const PT_BR_CONFIRM_ACTION_COPY: ConfirmActionCopy = {
|
|
26
|
+
cancel: "Cancelar",
|
|
27
|
+
defaultError: "Não foi possível concluir a ação. Tente novamente.",
|
|
28
|
+
irreversible: "Esta ação não pode ser desfeita.",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const PT_BR_CATEGORY_SELECT_COPY: CategorySelectCopy = {
|
|
32
|
+
purpose: "Categorias organizam o cardápio e os filtros da loja.",
|
|
33
|
+
clearSelection: "Limpar seleção",
|
|
34
|
+
emptyTitle: "Nenhuma categoria ainda",
|
|
35
|
+
createCategory: "Criar categoria",
|
|
36
|
+
noResults: {
|
|
37
|
+
title: (query) => `Nada encontrado para “${query}”`,
|
|
38
|
+
hint: "Tente outro termo ou verifique a grafia.",
|
|
39
|
+
clearSearch: "Limpar busca",
|
|
40
|
+
create: (query) => `Criar “${query}”`,
|
|
41
|
+
},
|
|
42
|
+
search: {
|
|
43
|
+
placeholderSingle: "Buscar categoria…",
|
|
44
|
+
placeholderMulti: "Buscar categoria ou subcategoria",
|
|
45
|
+
clear: "Limpar busca",
|
|
46
|
+
removeChip: (label) => `Remover ${label}`,
|
|
47
|
+
pinnedLabel: (count) => `Selecionadas · ${count}`,
|
|
48
|
+
},
|
|
49
|
+
footer: {
|
|
50
|
+
selectedCount: (count) => `${count} selecionada${count === 1 ? "" : "s"}`,
|
|
51
|
+
clear: "Limpar",
|
|
52
|
+
apply: "Aplicar",
|
|
53
|
+
close: "Fechar",
|
|
54
|
+
singleHint: "Escolha uma categoria",
|
|
55
|
+
cancel: "Cancelar",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const PT_BR_CEP_FIELD_COPY: CepFieldCopy = {
|
|
60
|
+
loading: "Buscando endereço…",
|
|
61
|
+
found: "Endereço preenchido pelo CEP.",
|
|
62
|
+
notfound: "CEP não encontrado — preencha o endereço manualmente.",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The saved-views dropdown's labels.
|
|
67
|
+
*
|
|
68
|
+
* Typed as the package's OWN `SavedViewsLabels` rather than a parallel shape:
|
|
69
|
+
* that interface already named every string, including the seven with no
|
|
70
|
+
* diacritics the copy gate could not see (`Aplicar`, `Fixar na barra lateral`,
|
|
71
|
+
* `Editar`…). The prop was `Partial<SavedViewsLabels>` with a pt-BR default —
|
|
72
|
+
* configurable in the type, inherited in practice.
|
|
73
|
+
*/
|
|
74
|
+
export const PT_BR_SAVED_VIEWS_LABELS: SavedViewsLabels = {
|
|
75
|
+
trigger: "Visões",
|
|
76
|
+
mainView: "Visão principal",
|
|
77
|
+
pinned: "Fixadas",
|
|
78
|
+
recent: "Recentes",
|
|
79
|
+
empty: "Nenhuma visão salva ainda.",
|
|
80
|
+
saveCurrent: "+ Salvar visão atual",
|
|
81
|
+
manageAll: "Gerenciar visões",
|
|
82
|
+
apply: "Aplicar",
|
|
83
|
+
setDefault: "Definir como padrão",
|
|
84
|
+
pin: "Fixar na barra lateral",
|
|
85
|
+
unpin: "Desafixar",
|
|
86
|
+
share: "Compartilhar com a equipe",
|
|
87
|
+
unshare: "Tornar privada",
|
|
88
|
+
edit: "Editar",
|
|
89
|
+
remove: "Excluir",
|
|
90
|
+
defaultTag: "Padrão",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const PT_BR_TABLE_FILTER_COPY: TableFilterCopy = {
|
|
94
|
+
invalidRange: "O máximo deve ser maior ou igual ao mínimo.",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const PT_BR_DATA_VIEWS_COPY: DataViewsCopy = {
|
|
98
|
+
savedViewsMenu: PT_BR_SAVED_VIEWS_LABELS,
|
|
99
|
+
confirmAction: PT_BR_CONFIRM_ACTION_COPY,
|
|
100
|
+
categorySelect: PT_BR_CATEGORY_SELECT_COPY,
|
|
101
|
+
board: {
|
|
102
|
+
countOnPage: (label, count) => `${label}: ${count} nesta página`,
|
|
103
|
+
onThisPage: (count) => `${count} nesta página`,
|
|
104
|
+
pageSum: (sum) => `Soma desta página: ${sum}`,
|
|
105
|
+
pageScopeNote:
|
|
106
|
+
"As contagens e somas abaixo referem-se apenas aos itens desta página.",
|
|
107
|
+
},
|
|
108
|
+
deleteView: {
|
|
109
|
+
title: "Excluir visão",
|
|
110
|
+
sharedWarning: "Esta visão está compartilhada — a equipe perderá o acesso a ela.",
|
|
111
|
+
rowsUnaffected: (entityLabel) =>
|
|
112
|
+
`${entityLabel} não são afetados; apenas o recorte salvo é removido.`,
|
|
113
|
+
confirm: "Excluir visão",
|
|
114
|
+
},
|
|
115
|
+
manageViews: {
|
|
116
|
+
title: "Gerenciar visões",
|
|
117
|
+
empty: "Nenhuma visão salva.",
|
|
118
|
+
defaultTag: "Padrão",
|
|
119
|
+
otherUserTag: "De outro usuário",
|
|
120
|
+
deleteTitle: "Excluir a visão salva?",
|
|
121
|
+
deleteBody: "Os filtros e colunas guardados nela são perdidos. Não é possível restaurar.",
|
|
122
|
+
deleteConfirm: "Excluir",
|
|
123
|
+
},
|
|
124
|
+
saveView: {
|
|
125
|
+
sharedDescription: "Qualquer pessoa da loja poderá abrir e usar esta visão.",
|
|
126
|
+
setDefaultTitle: "Definir como padrão",
|
|
127
|
+
setDefaultDescription: "Esta tela abre nesta visão em vez da Visão principal.",
|
|
128
|
+
previewHeading: "O que esta visão guarda",
|
|
129
|
+
previewUnchanged: "Nada foi alterado — esta visão ficará igual à Visão principal.",
|
|
130
|
+
sortRowLabel: "Ordenação",
|
|
131
|
+
saveFailed: "Não foi possível salvar a visão.",
|
|
132
|
+
titleEditing: "Editar visão",
|
|
133
|
+
titleCreating: "Salvar visão",
|
|
134
|
+
descriptionLabel: "Descrição — opcional",
|
|
135
|
+
descriptionPlaceholder: "Para que serve esta visão",
|
|
136
|
+
submitEditing: "Salvar alterações",
|
|
137
|
+
submitCreating: "Salvar visão",
|
|
138
|
+
nameLabel: "Nome",
|
|
139
|
+
cancel: "Cancelar",
|
|
140
|
+
pinnedTitle: "Fixar na barra lateral",
|
|
141
|
+
pinnedDescription: "Aparece como atalho no menu, abaixo desta tela.",
|
|
142
|
+
sharedTitle: "Compartilhar com a equipe",
|
|
143
|
+
namePlaceholder: "Ex.: Recusados no PIX desta semana",
|
|
144
|
+
previewFilters: "Filtros",
|
|
145
|
+
previewHiddenColumns: "Colunas ocultas",
|
|
146
|
+
},
|
|
147
|
+
columns: {
|
|
148
|
+
visibleCount: (visible, total) => `${visible} de ${total} visíveis`,
|
|
149
|
+
reset: "Padrão",
|
|
150
|
+
showAll: "Mostrar todas",
|
|
151
|
+
},
|
|
152
|
+
display: {
|
|
153
|
+
sortTab: "Ordenar",
|
|
154
|
+
columnsTab: "Colunas",
|
|
155
|
+
panelTab: "Exibição",
|
|
156
|
+
direction: "Direção",
|
|
157
|
+
layoutHints: {
|
|
158
|
+
table: "Colunas comparáveis, boa para varrer números.",
|
|
159
|
+
list: "Uma linha por item, com os dados principais.",
|
|
160
|
+
cards: "Blocos maiores, bom para poucos itens.",
|
|
161
|
+
board: "Colunas por etapa, para acompanhar o que está onde.",
|
|
162
|
+
},
|
|
163
|
+
densityHeadings: {
|
|
164
|
+
table: "Altura das linhas",
|
|
165
|
+
list: "Altura das linhas",
|
|
166
|
+
cards: "Cards por linha",
|
|
167
|
+
board: "Largura das colunas",
|
|
168
|
+
},
|
|
169
|
+
densityLabels: {
|
|
170
|
+
table: { compact: "Baixa", cozy: "Média", comfortable: "Alta" },
|
|
171
|
+
list: { compact: "Baixa", cozy: "Média", comfortable: "Alta" },
|
|
172
|
+
cards: { compact: "Muitos", cozy: "Médio", comfortable: "Poucos" },
|
|
173
|
+
board: { compact: "Estreita", cozy: "Média", comfortable: "Larga" },
|
|
174
|
+
},
|
|
175
|
+
boardUnavailable: "Esta tela não declara etapas, então não oferece quadro.",
|
|
176
|
+
densityUnavailableNarrow:
|
|
177
|
+
"Um card por linha nesta largura — a densidade volta em telas maiores.",
|
|
178
|
+
trigger: "Exibir",
|
|
179
|
+
},
|
|
180
|
+
export: {
|
|
181
|
+
formats: {
|
|
182
|
+
json: { label: "JSON (.json)", hint: "Para integrações" },
|
|
183
|
+
},
|
|
184
|
+
visibleColumns: (columnCount) => `${columnCount} colunas visíveis, na ordem atual`,
|
|
185
|
+
trigger: "Exportar",
|
|
186
|
+
},
|
|
187
|
+
grid: {
|
|
188
|
+
rowActions: "Ações",
|
|
189
|
+
bulkActions: (selectedCount) => `Ações (${selectedCount}) ▾`,
|
|
190
|
+
},
|
|
191
|
+
filters: {
|
|
192
|
+
moreFilters: (count) => `Mais filtros: ${count} sem espaço na barra`,
|
|
193
|
+
moreFiltersApplied: (count, appliedCount) =>
|
|
194
|
+
`Mais filtros: ${count} sem espaço na barra, ${appliedCount} aplicado(s)`,
|
|
195
|
+
overflowNote: "sem espaço na barra",
|
|
196
|
+
rangeEnd: "Até",
|
|
197
|
+
rangeInvalid: "O início precisa ser menor que o fim.",
|
|
198
|
+
rangePresets: {
|
|
199
|
+
hoje: "Hoje",
|
|
200
|
+
ontem: "Ontem",
|
|
201
|
+
semana: "Esta semana",
|
|
202
|
+
mes: "Este mês",
|
|
203
|
+
ano: "Este ano",
|
|
204
|
+
},
|
|
205
|
+
scopesLabel: "Situação",
|
|
206
|
+
panelTitle: "Filtros",
|
|
207
|
+
clear: "Limpar",
|
|
208
|
+
clearAll: "Limpar filtros",
|
|
209
|
+
clearRange: (label) => `Limpar ${label}`,
|
|
210
|
+
allOption: "Todas",
|
|
211
|
+
optionSearchPlaceholder: "Buscar…",
|
|
212
|
+
optionsEmpty: "Nenhum resultado",
|
|
213
|
+
},
|
|
214
|
+
selection: {
|
|
215
|
+
selectAllOnPage: "Selecionar todos nesta página",
|
|
216
|
+
clearSelection: "Limpar seleção",
|
|
217
|
+
selectAll: "Selecionar tudo",
|
|
218
|
+
onThisPage: (count) => `${count} nesta página`,
|
|
219
|
+
selectRow: "Selecionar",
|
|
220
|
+
},
|
|
221
|
+
nav: {
|
|
222
|
+
mainView: "Visão principal",
|
|
223
|
+
defaultTag: "Padrão",
|
|
224
|
+
viewOptions: (viewName) => `Opções de ${viewName}`,
|
|
225
|
+
emptyHint: "Nenhuma visão salva. Ajuste filtros, colunas ou ordenação e salve abaixo.",
|
|
226
|
+
setDefault: "Definir como padrão",
|
|
227
|
+
deleteView: "Excluir visão",
|
|
228
|
+
unsavedChanges: "Alterações não salvas",
|
|
229
|
+
label: "Visão",
|
|
230
|
+
update: "Atualizar visão",
|
|
231
|
+
save: "Salvar visão",
|
|
232
|
+
updateFailed: "Não foi possível atualizar a visão",
|
|
233
|
+
saveFailed: "Não foi possível salvar a alteração da visão.",
|
|
234
|
+
editView: "Renomear e editar",
|
|
235
|
+
pinToSidebar: "Fixar na barra lateral",
|
|
236
|
+
shareWithTeam: "Compartilhar com a equipe",
|
|
237
|
+
},
|
|
238
|
+
search: {
|
|
239
|
+
placeholder: "Buscar…",
|
|
240
|
+
allColumnsLabel: "Buscar em todas as colunas",
|
|
241
|
+
keywordPlaceholder: "Pressione Enter para filtrar",
|
|
242
|
+
open: "Buscar",
|
|
243
|
+
close: "Fechar busca",
|
|
244
|
+
clear: "Limpar busca",
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/feedback/ConfirmAction/ConfirmAction.hooks.ts","../src/components/feedback/ConfirmAction/ConfirmAction.parts.tsx","../src/components/feedback/ConfirmAction/ConfirmAction.tsx","../src/components/feedback/ConfirmAction/with-confirmation.tsx","../src/components/feedback/ConfirmAction/ConfirmButton.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { ConfirmHandler, UseConfirmActionResult } from './ConfirmAction.types';\n\n/** Used when the guarded action rejects and the caller supplied no wording. */\nexport const DEFAULT_ERROR_TEXT = 'Não foi possível concluir a ação. Tente novamente.';\n\n/**\n * What the popup says after a failed attempt.\n *\n * A rejection carrying a message wins: the server's sentence (\"esta categoria\n * ainda tem produtos vinculados\") is the one the operator can act on, and it is\n * strictly better than the generic fallback. Handlers that have nothing useful\n * to say reject with nothing and get `errorText`.\n */\nfunction failureMessage(cause: unknown, errorText: string): string {\n if (cause instanceof Error && cause.message) return cause.message;\n return errorText;\n}\n\n/**\n * The confirmation state machine, headless.\n *\n * It is the whole safety contract in one place, so every consumer gets the same\n * behaviour: the action runs ONLY from `confirm`, never from opening the popup;\n * a second confirm while the first is in flight is dropped (a double-click\n * cannot send two deletes); a rejection keeps the popup open carrying the\n * failure instead of closing over a write that never happened.\n */\nexport function useConfirmAction(\n onConfirm: ConfirmHandler,\n errorText: string = DEFAULT_ERROR_TEXT,\n): UseConfirmActionResult {\n const [open, setOpen] = useState(false);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // `pending` is mirrored in a ref because the two guards that matter — drop a\n // second confirm, ignore a cancel mid-flight — are read inside callbacks that\n // must not be rebuilt on every state change. Reading the state variable there\n // would leave a stale closure holding `false` for the whole in-flight window,\n // which is exactly the double-submit this component exists to prevent.\n const busy = useRef(false);\n\n // The action can settle after the trigger has unmounted (a row deleted from\n // under its own menu), so the settle path must not touch state by then.\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const request = useCallback((): void => {\n setError(null);\n setOpen(true);\n }, []);\n\n const cancel = useCallback((): void => {\n // Inert mid-flight: the write has already left, and closing here would hide\n // its outcome.\n if (busy.current) return;\n setOpen(false);\n setError(null);\n }, []);\n\n const settle = useCallback((failure: string | null): void => {\n busy.current = false;\n if (!mounted.current) return;\n setPending(false);\n setError(failure);\n if (!failure) setOpen(false);\n }, []);\n\n const confirm = useCallback((): void => {\n if (busy.current) return;\n busy.current = true;\n setPending(true);\n setError(null);\n // The handler is invoked synchronously, exactly as a plain `onClick` would\n // be — only the OUTCOME is awaited. The try/catch is what puts a handler\n // that throws synchronously on the same failure path as one that returns a\n // rejected promise.\n try {\n const result = onConfirm();\n void Promise.resolve(result)\n .then(() => settle(null))\n .catch((cause: unknown) => settle(failureMessage(cause, errorText)));\n } catch (cause) {\n settle(failureMessage(cause, errorText));\n }\n }, [errorText, onConfirm, settle]);\n\n return { open, pending, error, request, cancel, confirm };\n}\n","import { Alert, Stack, TextField, Typography } from '@mui/material';\nimport React, { type ReactNode } from 'react';\n\nimport { AlertDialog } from '../../data-display/AlertDialog';\n\nimport type {\n ConfirmInitialFocus,\n ConfirmOptions,\n ConfirmTone,\n UseConfirmActionResult,\n} from './ConfirmAction.types';\n\nconst DEFAULT_CANCEL_TEXT = 'Cancelar';\nconst DEFAULT_TEST_ID = 'confirm-action';\n\n/**\n * The popup's body: the row being acted on, then what happens to it.\n *\n * The NAME is rendered in its own right rather than woven into the sentence, so\n * it is there whatever the caller wrote — \"the popup names the specific entity\"\n * is the one thing that must not depend on each call site remembering to\n * mention it. A body with neither a name nor a description would leave the\n * operator with only the title to decide on, so a neutral fallback covers that.\n */\nfunction confirmBody(description: ReactNode, entityName?: string): ReactNode {\n if (!entityName) return description ?? \"Esta ação não pode ser desfeita.\";\n return (\n <>\n <Typography component=\"span\" sx={{ fontWeight: 600, display: \"block\" }}>\n {entityName}\n </Typography>\n {description}\n </>\n );\n}\n\n/** Destructive dialogs open on Cancel; anything else opens on the confirm. */\nfunction resolveInitialFocus(\n tone: ConfirmTone,\n initialFocus?: ConfirmInitialFocus,\n): ConfirmInitialFocus {\n if (initialFocus) return initialFocus;\n return tone === 'destructive' ? 'cancel' : 'confirm';\n}\n\n/**\n * The type-to-confirm field. Rendered only when `typeToConfirm` is set, which\n * should be rare: it is the guard for actions with NO recovery path, where a\n * click is too cheap a gesture. Comparison is exact — trimmed of surrounding\n * whitespace only, so a trailing space from a paste does not read as a mismatch.\n */\nfunction TypeToConfirmField({\n expected,\n label,\n value,\n onChange,\n disabled,\n dataTestId,\n}: {\n expected: string;\n label?: string;\n value: string;\n onChange: (next: string) => void;\n disabled: boolean;\n dataTestId: string;\n}): React.ReactElement {\n return (\n <Stack spacing={1} sx={{ mt: 2 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {label ?? `Digite \"${expected}\" para confirmar.`}\n </Typography>\n <TextField\n size=\"small\"\n value={value}\n disabled={disabled}\n onChange={(event) => onChange(event.target.value)}\n // On the INPUT rather than the wrapper: a test id on MUI's TextField\n // lands on the outer div, which is not the node a test can type into.\n inputProps={{\n 'aria-label': label ?? `Digite ${expected} para confirmar`,\n 'data-testid': `${dataTestId}-type-to-confirm`,\n }}\n />\n </Stack>\n );\n}\n\nexport interface ConfirmActionDialogProps extends ConfirmOptions {\n state: UseConfirmActionResult;\n}\n\n/**\n * The popup itself: an `AlertDialog` in its destructive skin, plus the two\n * things a bare AlertDialog has no opinion about — the failure the last attempt\n * produced (shown IN the dialog, which stays open, rather than closing over a\n * write that never landed) and the optional type-to-confirm gate.\n */\nexport function ConfirmActionDialog({\n state,\n title,\n description,\n entityName,\n confirmText,\n cancelText = DEFAULT_CANCEL_TEXT,\n typeToConfirm,\n typeToConfirmLabel,\n tone = 'destructive',\n initialFocus,\n dataTestId = DEFAULT_TEST_ID,\n}: ConfirmActionDialogProps): React.ReactElement {\n const [typed, setTyped] = React.useState('');\n\n // Every fresh open starts from an empty field: carrying the previous answer\n // over would hand the next delete a pre-satisfied gate.\n React.useEffect(() => {\n if (state.open) setTyped('');\n }, [state.open]);\n\n const gateUnmet = typeToConfirm !== undefined && typed.trim() !== typeToConfirm;\n\n return (\n <AlertDialog\n open={state.open}\n variant={tone === 'destructive' ? 'destructive' : 'default'}\n title={title}\n description={confirmBody(description, entityName)}\n confirmText={confirmText}\n cancelText={cancelText}\n loading={state.pending}\n confirmDisabled={gateUnmet}\n initialFocus={resolveInitialFocus(tone, initialFocus)}\n onConfirm={state.confirm}\n onCancel={state.cancel}\n onClose={state.cancel}\n data-testid={dataTestId}\n >\n {state.error && (\n <Alert severity=\"error\" sx={{ mt: 1 }} data-testid={`${dataTestId}-error`}>\n {state.error}\n </Alert>\n )}\n {typeToConfirm !== undefined && (\n <TypeToConfirmField\n expected={typeToConfirm}\n label={typeToConfirmLabel}\n value={typed}\n onChange={setTyped}\n disabled={state.pending}\n dataTestId={dataTestId}\n />\n )}\n </AlertDialog>\n );\n}\n","import React from 'react';\n\nimport { useConfirmAction } from './ConfirmAction.hooks';\nimport { ConfirmActionDialog } from './ConfirmAction.parts';\nimport type { ConfirmActionProps } from './ConfirmAction.types';\n\n/**\n * Wraps any trigger in a confirmation popup.\n *\n * The trigger is a render prop rather than a fixed element, so the same guard\n * covers a button, a dropdown item, an icon or a table row action without this\n * component knowing what any of them look like. The guarded handler runs ONLY\n * after the operator confirms:\n *\n * ```tsx\n * <ConfirmAction\n * title=\"Excluir a categoria?\"\n * entityName={row.name}\n * description=\"Ela vai para a lixeira e pode ser restaurada de lá.\"\n * confirmText=\"Excluir\"\n * onConfirm={() => deleteCategoryAction({ tenantSlug, id: row.id })}\n * >\n * {(request) => <Button color=\"danger\" onClick={request}>Excluir</Button>}\n * </ConfirmAction>\n * ```\n *\n * For the common case — an existing button whose `onClick` is already the\n * destructive write — reach for {@link withConfirmation} instead, which needs\n * no restructuring of the call site.\n */\nexport function ConfirmAction({\n onConfirm,\n children,\n ...options\n}: ConfirmActionProps): React.ReactElement {\n const state = useConfirmAction(onConfirm, options.errorText);\n\n return (\n <>\n {children(state.request, { pending: state.pending })}\n <ConfirmActionDialog state={state} {...options} />\n </>\n );\n}\n\nConfirmAction.displayName = 'ConfirmAction';\n","import React from 'react';\nimport type { ComponentType } from 'react';\n\nimport { useConfirmAction } from './ConfirmAction.hooks';\nimport { ConfirmActionDialog } from './ConfirmAction.parts';\nimport type {\n ConfirmHandler,\n ConfirmOptions,\n ConfirmableComponent,\n ConfirmableProps,\n WithConfirmationProps,\n} from './ConfirmAction.types';\n\n/**\n * Merge the HOC's baked-in defaults with the per-instance `confirm` prop.\n * Returns null when the instance opts out (`confirm={false}`) or when neither\n * side supplies the two fields a confirmation cannot be written without — the\n * question and the verb. A null result means \"render the plain component\",\n * which keeps the wrapped export usable in harmless contexts.\n */\nfunction resolveOptions(\n defaults: Partial<ConfirmOptions> | undefined,\n instance: Partial<ConfirmOptions> | false | undefined,\n): ConfirmOptions | null {\n if (instance === false) return null;\n const merged = { ...defaults, ...instance };\n if (!merged.title || !merged.confirmText) return null;\n return { ...merged, title: merged.title, confirmText: merged.confirmText };\n}\n\n/**\n * Adapt the wrapped component's `onClick` into a {@link ConfirmHandler}.\n *\n * The click argument is dropped (see `ConfirmHandler`) and the return value is\n * narrowed: a promise is handed back so the popup can hold its pending state\n * until the write settles, anything else becomes `undefined`. A component with\n * no `onClick` has nothing to guard, so confirming is a no-op.\n */\nfunction useGuardedHandler(onClick: ConfirmableProps['onClick']): ConfirmHandler {\n return React.useCallback((): void | Promise<unknown> => {\n const result = onClick?.();\n return result instanceof Promise ? result : undefined;\n }, [onClick]);\n}\n\n/**\n * Give any click-driven component a confirmation popup, without restructuring\n * the call site.\n *\n * The wrapped component's own `onClick` becomes the GUARDED action: clicking\n * now opens the popup, and the handler runs only if the operator confirms. It\n * inherits the whole contract from `useConfirmAction` — cancel sends nothing, a\n * returned promise holds the popup in its pending state so a double-click\n * cannot fire two writes, and a rejection keeps the popup open carrying the\n * error.\n *\n * ```tsx\n * const ConfirmButton = withConfirmation(Button);\n *\n * <ConfirmButton\n * color=\"danger\"\n * onClick={() => deleteCategoryAction({ tenantSlug, id: row.id })}\n * confirm={{\n * title: \"Excluir a categoria?\",\n * entityName: row.name,\n * description: \"Ela vai para a lixeira e pode ser restaurada de lá.\",\n * confirmText: \"Excluir\",\n * }}\n * >\n * Excluir\n * </ConfirmButton>\n * ```\n *\n * Bake the wording in once when the same guard repeats — `withConfirmation(Button,\n * { confirmText: \"Excluir\" })` — and let each instance override what differs.\n * `confirm={false}` falls through to the plain component.\n *\n * Refs are not forwarded: the HOC renders a fragment (trigger + popup), so there\n * is no single node to hand back. Wrap a component that needs a ref at its own\n * call site with {@link ConfirmAction} instead.\n */\nexport function withConfirmation<P extends ConfirmableProps>(\n Wrapped: ComponentType<P>,\n defaults?: Partial<ConfirmOptions>,\n): ConfirmableComponent<P> {\n function WithConfirmation(props: P & WithConfirmationProps): React.ReactElement {\n const { confirm, onClick } = props;\n const options = resolveOptions(defaults, confirm);\n const state = useConfirmAction(useGuardedHandler(onClick), options?.errorText);\n\n // `confirm` is ours and must not reach the DOM — an unknown attribute on a\n // <button> is a React warning at every render. The cast is the standard HOC\n // one: TypeScript cannot prove `Omit<P & X, 'confirm'>` is still a `P`.\n const { confirm: _confirm, ...rest } = props;\n const forwarded = rest as unknown as P;\n\n if (!options) return <Wrapped {...forwarded} />;\n\n return (\n <>\n <Wrapped {...forwarded} onClick={state.request} />\n <ConfirmActionDialog state={state} {...options} />\n </>\n );\n }\n\n WithConfirmation.displayName = `withConfirmation(${\n Wrapped.displayName ?? Wrapped.name ?? 'Component'\n })`;\n\n return WithConfirmation;\n}\n","import { Button } from '../../form/Button';\nimport type { ButtonProps } from '../../form/Button/Button.types';\n\nimport { withConfirmation } from './with-confirmation';\nimport type { ConfirmableComponent } from './ConfirmAction.types';\n\n/**\n * The library `Button`, already wrapped — the shape most call sites want.\n *\n * Adding the guard to an existing button is then an import swap plus the\n * `confirm` prop; the button's own props (colour, size, icon) are untouched.\n * The baked-in defaults are only the wording and tone every destructive\n * confirmation shares, and each instance can still override them.\n */\nexport const ConfirmButton: ConfirmableComponent<ButtonProps> = withConfirmation<ButtonProps>(\n Button,\n { cancelText: 'Cancelar', tone: 'destructive' },\n);\n"],"mappings":"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","names":["Fragment","jsx","jsxs","React","Fragment","jsx","jsxs","React"]}
|