@aiaiai-pt/design-system 0.24.0 → 0.26.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.
@@ -85,6 +85,22 @@
85
85
  /** Inline error rendered by the geo MapPicker(s) (e.g. the consumer's
86
86
  * out-of-bounds copy) — forwarded as MapPicker's `error`. */
87
87
  geoError?: string;
88
+ /** Service-flow wizard (#105 P6): render ONLY the section whose
89
+ * name (its label, falling back to its key) matches — one screen per
90
+ * step. The value bag still spans ALL parameters (so answers persist
91
+ * across steps and the final payload is complete); only the DISPLAY is
92
+ * scoped. Null (default) renders every section, byte-identical to today. */
93
+ activeSectionKey?: string | null;
94
+ /** Service-flow wizard (#105 P6): suppress the renderer's own submit
95
+ * button / captcha / error — the wizard shell owns the nav and posts the
96
+ * payload once at the end. Default false (the standalone form keeps its
97
+ * button). */
98
+ hideSubmit?: boolean;
99
+ /** Service-flow wizard (#105 P6): fires with the freshly built payload
100
+ * whenever a value or attachment changes, so the wizard always holds the
101
+ * complete payload (incl. attachment_keys) to review and submit. Read-only
102
+ * consumers omit it. */
103
+ onchange?: (payload: Record<string, unknown>) => void;
88
104
  }
89
105
 
90
106
  let {
@@ -102,6 +118,9 @@
102
118
  layers = [],
103
119
  onoutofbounds = undefined,
104
120
  geoError = undefined,
121
+ activeSectionKey = null,
122
+ hideSubmit = false,
123
+ onchange = undefined,
105
124
  }: Props = $props();
106
125
 
107
126
  let values = $state<Record<string, unknown>>({});
@@ -125,7 +144,9 @@
125
144
  // consumer wired an apply. Preview modes (admin-preview/adapter-preview) and a
126
145
  // missing onApply stay read-only — the button never renders.
127
146
  const isSubmitMode = $derived(mode === "public-submit" || mode === "admin-execute");
128
- const showSubmit = $derived(isSubmitMode && onApply !== undefined);
147
+ // The wizard (#105 P6) suppresses the internal button via hideSubmit — it
148
+ // owns the nav and posts once at the end.
149
+ const showSubmit = $derived(isSubmitMode && onApply !== undefined && !hideSubmit);
129
150
 
130
151
  const renderedAction = $derived((schema?.action as Entity | null | undefined) ?? action);
131
152
  const renderedPlacement = $derived((schema?.placement as Entity | null | undefined) ?? placement);
@@ -146,9 +167,23 @@
146
167
  );
147
168
  const schemaSections = $derived(schemaSectionsFromContract());
148
169
  const sections = $derived(schemaSections ?? groupIntoSections(visibleParameters));
170
+ // Wizard pagination (#105 P6): when a section is active, render only it. The
171
+ // value bag is unaffected — every parameter is still seeded and submitted.
172
+ const displaySections = $derived(
173
+ activeSectionKey == null
174
+ ? sections
175
+ : sections.filter((section) => section.name === activeSectionKey),
176
+ );
149
177
  const payload = $derived(buildPayload());
150
178
  const payloadJson = $derived(JSON.stringify(payload, null, 2));
151
179
 
180
+ // Wizard (#105 P6): surface the live payload so the shell can review + submit
181
+ // it. Reading `payload` registers the dependency; the effect re-runs whenever
182
+ // a value or attachment changes.
183
+ $effect(() => {
184
+ onchange?.(payload);
185
+ });
186
+
152
187
  // Client-side submit gate (layer 1 — see the ADL): every required, visible
153
188
  // parameter must have a non-empty value. Server-side submission criteria are
154
189
  // enforced by the BFF on submit and surfaced via `submitError`.
@@ -520,21 +555,26 @@
520
555
  {/snippet}
521
556
 
522
557
  <div class="renderer" data-testid={`action-form-renderer-${mode}`} data-layout={resolvedLayout.key}>
523
- <div class="renderer-header">
524
- <div>
525
- <!-- The placement-preview eyebrow + surface badge are operator chrome
526
- hidden in public-submit so a citizen sees a clean form title. -->
558
+ <!-- Wizard pagination (#105 P6): the ServiceFlow shell owns the step
559
+ heading, so the renderer's own action-title header is suppressed when a
560
+ section is active to avoid a duplicate heading per step. -->
561
+ {#if activeSectionKey == null}
562
+ <div class="renderer-header">
563
+ <div>
564
+ <!-- The placement-preview eyebrow + surface badge are operator chrome —
565
+ hidden in public-submit so a citizen sees a clean form title. -->
566
+ {#if mode !== "public-submit"}
567
+ <p class="eyebrow">Placement preview</p>
568
+ {/if}
569
+ <h3>{String(renderedAction?.label ?? renderedAction?.key ?? "Select an action")}</h3>
570
+ </div>
527
571
  {#if mode !== "public-submit"}
528
- <p class="eyebrow">Placement preview</p>
572
+ <Badge variant={renderedPlacement ? "info" : "neutral"}>
573
+ {String(renderedPlacement?.surface ?? "No placement")}
574
+ </Badge>
529
575
  {/if}
530
- <h3>{String(renderedAction?.label ?? renderedAction?.key ?? "Select an action")}</h3>
531
576
  </div>
532
- {#if mode !== "public-submit"}
533
- <Badge variant={renderedPlacement ? "info" : "neutral"}>
534
- {String(renderedPlacement?.surface ?? "No placement")}
535
- </Badge>
536
- {/if}
537
- </div>
577
+ {/if}
538
578
 
539
579
  {#if !renderedAction}
540
580
  <p class="muted">Select an action to preview its placement-aware form contract.</p>
@@ -550,7 +590,7 @@
550
590
  renderedAction?.label ?? renderedAction?.key ?? "Form",
551
591
  )}
552
592
  >
553
- <Layout {sections} field={fieldRow} />
593
+ <Layout sections={displaySections} field={fieldRow} />
554
594
  </form>
555
595
  {/if}
556
596
 
@@ -70,6 +70,22 @@ interface Props {
70
70
  /** Inline error rendered by the geo MapPicker(s) (e.g. the consumer's
71
71
  * out-of-bounds copy) — forwarded as MapPicker's `error`. */
72
72
  geoError?: string;
73
+ /** Service-flow wizard (#105 P6): render ONLY the section whose
74
+ * name (its label, falling back to its key) matches — one screen per
75
+ * step. The value bag still spans ALL parameters (so answers persist
76
+ * across steps and the final payload is complete); only the DISPLAY is
77
+ * scoped. Null (default) renders every section, byte-identical to today. */
78
+ activeSectionKey?: string | null;
79
+ /** Service-flow wizard (#105 P6): suppress the renderer's own submit
80
+ * button / captcha / error — the wizard shell owns the nav and posts the
81
+ * payload once at the end. Default false (the standalone form keeps its
82
+ * button). */
83
+ hideSubmit?: boolean;
84
+ /** Service-flow wizard (#105 P6): fires with the freshly built payload
85
+ * whenever a value or attachment changes, so the wizard always holds the
86
+ * complete payload (incl. attachment_keys) to review and submit. Read-only
87
+ * consumers omit it. */
88
+ onchange?: (payload: Record<string, unknown>) => void;
73
89
  }
74
90
  declare const ActionFormRenderer: import("svelte").Component<Props, {}, "">;
75
91
  type ActionFormRenderer = ReturnType<typeof ActionFormRenderer>;
@@ -0,0 +1,180 @@
1
+ <!--
2
+ @component CallToAction
3
+
4
+ A conversion band: a heading, optional body, and one or two action buttons.
5
+ The reusable "ready to act?" strip at the foot of landing / campaign / content
6
+ pages. Presentational — heading, body and buttons (label + href) are DATA, so
7
+ it drops into any vertical.
8
+
9
+ `variant="accent"` fills the band with the brand accent (text flips to
10
+ `--color-text-on-accent`); `"surface"` (default) is a quiet bordered band.
11
+
12
+ @example
13
+ <CallToAction
14
+ heading="Tem uma ideia para o seu município?"
15
+ body="Submeta a sua proposta ao Orçamento Participativo."
16
+ primary={{ label: "Submeter proposta", href: "/propor" }}
17
+ secondary={{ label: "Ver propostas", href: "/mapa" }}
18
+ variant="accent"
19
+ />
20
+ -->
21
+ <script>
22
+ import Button from "./Button.svelte";
23
+
24
+ let {
25
+ /** @type {string} */
26
+ heading = "",
27
+ /** @type {string} */
28
+ body = "",
29
+ /** @type {2 | 3} Heading level for `heading`. */
30
+ headingLevel = 2,
31
+ /** @type {{ label?: string, href?: string } | undefined} Primary CTA. */
32
+ primary = undefined,
33
+ /** @type {{ label?: string, href?: string } | undefined} Secondary CTA. */
34
+ secondary = undefined,
35
+ /** @type {'surface' | 'accent'} Band fill. */
36
+ variant = "surface",
37
+ /** @type {string} */
38
+ class: className = "",
39
+ ...rest
40
+ } = $props();
41
+ </script>
42
+
43
+ <section class="cta cta-{variant} {className}" {...rest}>
44
+ <div class="cta-inner">
45
+ {#if heading}
46
+ <svelte:element this={`h${headingLevel}`} class="cta-heading"
47
+ >{heading}</svelte:element
48
+ >
49
+ {/if}
50
+ {#if body}<p class="cta-body">{body}</p>{/if}
51
+ {#if (primary && primary.label) || (secondary && secondary.label)}
52
+ <div class="cta-actions">
53
+ {#if variant === "accent"}
54
+ <!-- On an accent (brand-coloured) fill the DS button variants don't
55
+ guarantee contrast (the theme's `secondary`/`ghost` text/bg sit
56
+ too close to the accent). Render explicit INVERTED controls whose
57
+ colours are the on-accent/accent token pair — the same contrast
58
+ ratio as a primary button, just flipped. -->
59
+ {#if primary && primary.label}
60
+ <a class="cta-btn-inverted" href={primary.href || undefined}
61
+ >{primary.label}</a
62
+ >
63
+ {/if}
64
+ {#if secondary && secondary.label}
65
+ <a class="cta-link-on-accent" href={secondary.href || undefined}
66
+ >{secondary.label}</a
67
+ >
68
+ {/if}
69
+ {:else}
70
+ {#if primary && primary.label}
71
+ <Button href={primary.href || undefined} variant="primary"
72
+ >{primary.label}</Button
73
+ >
74
+ {/if}
75
+ {#if secondary && secondary.label}
76
+ <Button href={secondary.href || undefined} variant="ghost"
77
+ >{secondary.label}</Button
78
+ >
79
+ {/if}
80
+ {/if}
81
+ </div>
82
+ {/if}
83
+ </div>
84
+ </section>
85
+
86
+ <style>
87
+ .cta {
88
+ border-radius: var(--card-radius);
89
+ overflow-wrap: break-word;
90
+ }
91
+
92
+ .cta-surface {
93
+ background: var(--color-surface-secondary);
94
+ border: 1px solid var(--color-border);
95
+ }
96
+
97
+ .cta-accent {
98
+ background: var(--color-accent);
99
+ color: var(--color-text-on-accent);
100
+ }
101
+
102
+ .cta-inner {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: var(--space-md);
106
+ align-items: flex-start;
107
+ max-width: var(--content-width-wide);
108
+ margin-inline: auto;
109
+ padding: var(--space-2xl) var(--content-padding-x);
110
+ }
111
+
112
+ .cta-heading {
113
+ margin: 0;
114
+ font-family: var(--type-heading-font);
115
+ font-size: var(--type-heading-size);
116
+ line-height: var(--type-heading-leading);
117
+ color: inherit;
118
+ }
119
+
120
+ .cta-surface .cta-heading {
121
+ color: var(--color-text);
122
+ }
123
+
124
+ .cta-body {
125
+ margin: 0;
126
+ max-width: var(--content-width-narrow);
127
+ font-family: var(--type-body-font);
128
+ font-size: var(--type-body-size);
129
+ line-height: var(--type-body-leading);
130
+ color: inherit;
131
+ }
132
+
133
+ .cta-surface .cta-body {
134
+ color: var(--color-text-secondary);
135
+ }
136
+
137
+ .cta-actions {
138
+ display: flex;
139
+ flex-wrap: wrap;
140
+ align-items: center;
141
+ gap: var(--space-md);
142
+ margin-top: var(--space-xs);
143
+ }
144
+
145
+ /* Inverted solid button for the accent band: the on-accent surface as the
146
+ fill, the accent as the text — the primary-button token pair, flipped, so
147
+ it carries the same contrast guarantee against the coloured band. */
148
+ .cta-btn-inverted {
149
+ display: inline-flex;
150
+ align-items: center;
151
+ height: var(--button-md-height);
152
+ padding-inline: var(--button-md-padding-x);
153
+ border-radius: var(--button-radius);
154
+ background: var(--color-text-on-accent);
155
+ color: var(--color-accent);
156
+ font-family: var(--button-font);
157
+ font-size: var(--button-md-font-size);
158
+ letter-spacing: var(--button-tracking);
159
+ text-decoration: none;
160
+ white-space: nowrap;
161
+ }
162
+
163
+ .cta-btn-inverted:focus-visible {
164
+ outline: var(--focus-ring-width) solid var(--color-text-on-accent);
165
+ outline-offset: var(--focus-ring-offset);
166
+ }
167
+
168
+ .cta-link-on-accent {
169
+ color: var(--color-text-on-accent);
170
+ font-family: var(--type-body-font);
171
+ font-size: var(--type-body-size);
172
+ text-decoration: underline;
173
+ text-underline-offset: 0.2em;
174
+ }
175
+
176
+ .cta-link-on-accent:focus-visible {
177
+ outline: var(--focus-ring-width) solid var(--color-text-on-accent);
178
+ outline-offset: var(--focus-ring-offset);
179
+ }
180
+ </style>
@@ -0,0 +1,43 @@
1
+ export default CallToAction;
2
+ type CallToAction = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * CallToAction
8
+ *
9
+ * A conversion band: a heading, optional body, and one or two action buttons.
10
+ * The reusable "ready to act?" strip at the foot of landing / campaign / content
11
+ * pages. Presentational — heading, body and buttons (label + href) are DATA, so
12
+ * it drops into any vertical.
13
+ *
14
+ * `variant="accent"` fills the band with the brand accent (text flips to
15
+ * `--color-text-on-accent`); `"surface"` (default) is a quiet bordered band.
16
+ *
17
+ * @example
18
+ * <CallToAction
19
+ * heading="Tem uma ideia para o seu município?"
20
+ * body="Submeta a sua proposta ao Orçamento Participativo."
21
+ * primary={{ label: "Submeter proposta", href: "/propor" }}
22
+ * secondary={{ label: "Ver propostas", href: "/mapa" }}
23
+ * variant="accent"
24
+ * />
25
+ */
26
+ declare const CallToAction: import("svelte").Component<{
27
+ heading?: string;
28
+ body?: string;
29
+ headingLevel?: number;
30
+ primary?: any;
31
+ secondary?: any;
32
+ variant?: string;
33
+ class?: string;
34
+ } & Record<string, any>, {}, "">;
35
+ type $$ComponentProps = {
36
+ heading?: string;
37
+ body?: string;
38
+ headingLevel?: number;
39
+ primary?: any;
40
+ secondary?: any;
41
+ variant?: string;
42
+ class?: string;
43
+ } & Record<string, any>;
@@ -0,0 +1,197 @@
1
+ <!--
2
+ @component CheckAnswers
3
+
4
+ The "review your answers before submitting" summary — a grouped description
5
+ list of every answer the citizen gave, each with a "Change" link back to the
6
+ step that captured it. Vertical-agnostic: it renders whatever label/value
7
+ pairs the consumer collected (from the action's parameter labels), grouped by
8
+ the flow's sections. Pure presentation — no state, no network.
9
+
10
+ Accessibility (WCAG 3.3.4, Error Prevention): every answer is reviewable and
11
+ editable before the irreversible submit. Each "Change" control carries an
12
+ accessible name that includes WHAT it changes (the visible "Change" plus a
13
+ visually-hidden field label), so a screen-reader user never meets a row of
14
+ identical "Change" links. Answers are a `<dl>` (the semantic structure for
15
+ term/description pairs); section headings are `<h3>` under the widget's `h2`.
16
+
17
+ @example
18
+ <CheckAnswers
19
+ label="Check your answers"
20
+ groups={[
21
+ { label: "About you", items: [
22
+ { key: "name", label: "Full name", value: "Ana Silva", onChange: () => goTo(0) },
23
+ ]},
24
+ { label: "Your idea", items: [
25
+ { key: "title", label: "Title", value: "Bike lanes", onChange: () => goTo(1) },
26
+ ]},
27
+ ]}
28
+ />
29
+ -->
30
+ <script module>
31
+ /**
32
+ * @typedef {{ key: string, label: string, value: string, onChange?: () => void }} AnswerItem
33
+ * @typedef {{ label?: string, items: AnswerItem[] }} AnswerGroup
34
+ */
35
+ </script>
36
+
37
+ <script>
38
+ let {
39
+ /** @type {AnswerGroup[]} The collected answers, grouped by section. */
40
+ groups = [],
41
+ /** @type {string} Accessible name for the review region (localize it). */
42
+ label = "Check your answers",
43
+ /** @type {string} Visible text on each change control (localize it). */
44
+ changeLabel = "Change",
45
+ /** @type {string} Shown when there are no answers yet (localize it). */
46
+ emptyText = "There are no answers to review yet.",
47
+ /** @type {string} Placeholder for an answer the citizen left blank (localize it). */
48
+ blankText = "Not provided",
49
+
50
+ /** @type {string} */
51
+ class: className = "",
52
+ ...rest
53
+ } = $props();
54
+
55
+ const hasAnswers = $derived(groups.some((g) => g.items.length > 0));
56
+ </script>
57
+
58
+ <section class="check-answers {className}" aria-label={label} {...rest}>
59
+ {#if hasAnswers}
60
+ {#each groups as group (group.label ?? group.items[0]?.key)}
61
+ {#if group.items.length > 0}
62
+ <div class="check-answers-group">
63
+ {#if group.label}
64
+ <h3 class="check-answers-group-heading">{group.label}</h3>
65
+ {/if}
66
+ <dl class="check-answers-list">
67
+ {#each group.items as item (item.key)}
68
+ <div class="check-answers-row">
69
+ <dt class="check-answers-term">{item.label}</dt>
70
+ <dd class="check-answers-value">
71
+ {item.value || blankText}
72
+ </dd>
73
+ <dd class="check-answers-action">
74
+ {#if item.onChange}
75
+ <button
76
+ type="button"
77
+ class="check-answers-change"
78
+ onclick={() => item.onChange?.()}
79
+ >
80
+ {changeLabel}<span class="check-answers-sr"
81
+ >&nbsp;{item.label}</span
82
+ >
83
+ </button>
84
+ {/if}
85
+ </dd>
86
+ </div>
87
+ {/each}
88
+ </dl>
89
+ </div>
90
+ {/if}
91
+ {/each}
92
+ {:else}
93
+ <p class="check-answers-empty">{emptyText}</p>
94
+ {/if}
95
+ </section>
96
+
97
+ <style>
98
+ .check-answers {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: var(--space-lg);
102
+ }
103
+
104
+ .check-answers-group {
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: var(--space-sm);
108
+ }
109
+
110
+ .check-answers-group-heading {
111
+ font-family: var(--type-heading-sm-font);
112
+ font-size: var(--type-heading-sm-size);
113
+ color: var(--color-text);
114
+ margin: 0;
115
+ }
116
+
117
+ .check-answers-list {
118
+ margin: 0;
119
+ display: flex;
120
+ flex-direction: column;
121
+ }
122
+
123
+ .check-answers-row {
124
+ display: grid;
125
+ grid-template-columns: minmax(8rem, 1fr) 2fr auto;
126
+ gap: var(--space-sm);
127
+ align-items: start;
128
+ padding: var(--space-sm) 0;
129
+ border-bottom: 1px solid var(--color-border);
130
+ }
131
+
132
+ .check-answers-term {
133
+ font-family: var(--type-body-sm-font);
134
+ font-size: var(--type-body-sm-size);
135
+ color: var(--color-text-muted);
136
+ margin: 0;
137
+ }
138
+
139
+ .check-answers-value {
140
+ font-family: var(--type-body-font);
141
+ font-size: var(--type-body-size);
142
+ color: var(--color-text);
143
+ margin: 0;
144
+ overflow-wrap: anywhere;
145
+ }
146
+
147
+ .check-answers-action {
148
+ margin: 0;
149
+ justify-self: end;
150
+ }
151
+
152
+ .check-answers-change {
153
+ appearance: none;
154
+ background: none;
155
+ border: none;
156
+ padding: 0;
157
+ cursor: pointer;
158
+ font-family: var(--type-body-sm-font);
159
+ font-size: var(--type-body-sm-size);
160
+ color: var(--color-accent);
161
+ text-decoration: underline;
162
+ text-underline-offset: 2px;
163
+ }
164
+
165
+ .check-answers-change:hover {
166
+ color: var(--color-accent-hover);
167
+ }
168
+
169
+ .check-answers-empty {
170
+ font-family: var(--type-body-font);
171
+ font-size: var(--type-body-size);
172
+ color: var(--color-text-muted);
173
+ margin: 0;
174
+ }
175
+
176
+ /* Visually-hidden text that screen readers still announce (WCAG 3.3.4). */
177
+ .check-answers-sr {
178
+ position: absolute;
179
+ width: 1px;
180
+ height: 1px;
181
+ padding: 0;
182
+ margin: -1px;
183
+ overflow: hidden;
184
+ clip: rect(0, 0, 0, 0);
185
+ white-space: nowrap;
186
+ border: 0;
187
+ }
188
+
189
+ @media (max-width: 30rem) {
190
+ .check-answers-row {
191
+ grid-template-columns: 1fr auto;
192
+ }
193
+ .check-answers-term {
194
+ grid-column: 1 / -1;
195
+ }
196
+ }
197
+ </style>
@@ -0,0 +1,60 @@
1
+ export default CheckAnswers;
2
+ export type AnswerItem = {
3
+ key: string;
4
+ label: string;
5
+ value: string;
6
+ onChange?: () => void;
7
+ };
8
+ export type AnswerGroup = {
9
+ label?: string;
10
+ items: AnswerItem[];
11
+ };
12
+ type CheckAnswers = {
13
+ $on?(type: string, callback: (e: any) => void): () => void;
14
+ $set?(props: Partial<$$ComponentProps>): void;
15
+ };
16
+ /**
17
+ * CheckAnswers
18
+ *
19
+ * The "review your answers before submitting" summary — a grouped description
20
+ * list of every answer the citizen gave, each with a "Change" link back to the
21
+ * step that captured it. Vertical-agnostic: it renders whatever label/value
22
+ * pairs the consumer collected (from the action's parameter labels), grouped by
23
+ * the flow's sections. Pure presentation — no state, no network.
24
+ *
25
+ * Accessibility (WCAG 3.3.4, Error Prevention): every answer is reviewable and
26
+ * editable before the irreversible submit. Each "Change" control carries an
27
+ * accessible name that includes WHAT it changes (the visible "Change" plus a
28
+ * visually-hidden field label), so a screen-reader user never meets a row of
29
+ * identical "Change" links. Answers are a `<dl>` (the semantic structure for
30
+ * term/description pairs); section headings are `<h3>` under the widget's `h2`.
31
+ *
32
+ * @example
33
+ * <CheckAnswers
34
+ * label="Check your answers"
35
+ * groups={[
36
+ * { label: "About you", items: [
37
+ * { key: "name", label: "Full name", value: "Ana Silva", onChange: () => goTo(0) },
38
+ * ]},
39
+ * { label: "Your idea", items: [
40
+ * { key: "title", label: "Title", value: "Bike lanes", onChange: () => goTo(1) },
41
+ * ]},
42
+ * ]}
43
+ * />
44
+ */
45
+ declare const CheckAnswers: import("svelte").Component<{
46
+ groups?: any[];
47
+ label?: string;
48
+ changeLabel?: string;
49
+ emptyText?: string;
50
+ blankText?: string;
51
+ class?: string;
52
+ } & Record<string, any>, {}, "">;
53
+ type $$ComponentProps = {
54
+ groups?: any[];
55
+ label?: string;
56
+ changeLabel?: string;
57
+ emptyText?: string;
58
+ blankText?: string;
59
+ class?: string;
60
+ } & Record<string, any>;