@visns-studio/visns-components 6.24.3 → 6.25.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/package.json +4 -2
- package/src/components/Autocomplete.jsx +189 -119
- package/src/components/DataGrid.jsx +472 -31
- package/src/components/Navigation.jsx +475 -51
- package/src/components/auth/ClientAuthFrame.jsx +5 -0
- package/src/components/auth/ClientAuthScreen.jsx +29 -0
- package/src/components/columns/ColumnRenderers.jsx +3 -46
- package/src/components/columns/StackedRow.jsx +186 -0
- package/src/components/controls/DataGridSearch.jsx +110 -2
- package/src/components/controls/DataGridSortSheet.jsx +155 -0
- package/src/components/generic/GenericAuth.jsx +50 -18
- package/src/components/generic/GenericDashboard.jsx +20 -1
- package/src/components/generic/GenericDetail.jsx +446 -259
- package/src/components/mapboxSearchBox.js +640 -0
- package/src/components/navBadges.js +63 -1
- package/src/components/navDrawer.js +147 -0
- package/src/components/sms/SmsThreadPanel.jsx +34 -6
- package/src/components/sms/smsHelpers.js +15 -0
- package/src/components/styles/ClientAuth.module.scss +39 -0
- package/src/components/styles/DataGrid.module.scss +158 -5
- package/src/components/styles/Field.module.scss +52 -1
- package/src/components/styles/Form.module.scss +82 -0
- package/src/components/styles/GenericClientPortal.module.scss +72 -20
- package/src/components/styles/GenericDashboard.module.scss +50 -0
- package/src/components/styles/GenericDetail.module.scss +63 -1
- package/src/components/styles/GenericDynamic.module.scss +23 -0
- package/src/components/styles/GenericFormBuilder.module.scss +11 -0
- package/src/components/styles/GenericIndex.module.scss +6 -1
- package/src/components/styles/Navigation.module.scss +460 -7
- package/src/components/styles/Sms.module.scss +92 -0
- package/src/components/styles/StackedRow.module.scss +182 -0
- package/src/components/styles/TicketConversation.module.scss +76 -0
- package/src/components/styles/Vault.module.scss +192 -0
- package/src/components/styles/density.css +10 -0
- package/src/components/styles/global-datagrid.css +220 -0
- package/src/components/styles/global.css +20 -0
- package/src/components/tickets/TicketConversation.jsx +13 -8
- package/src/components/utils/ConfirmDialog.js +22 -3
- package/src/components/utils/cardLayout.js +666 -0
- package/src/components/utils/contactChannels.js +130 -0
- package/src/components/utils/editPlacement.js +95 -0
- package/src/components/utils/useDensity.js +303 -7
- package/src/index.js +32 -0
|
@@ -103,6 +103,41 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
103
103
|
-webkit-line-clamp: 2 !important;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/* The same three rules again for a COARSE PRIMARY POINTER, which now switches
|
|
107
|
+
the grid into its fixed-row-height geometry on its own — see the long note
|
|
108
|
+
in utils/useDensity.js. A phone or a tablet gets the roomy rows and the 44px
|
|
109
|
+
targets with no view config at all; the `body.tablet-mode` class above still
|
|
110
|
+
works and now means "treat this view as touch even on a mouse".
|
|
111
|
+
*
|
|
112
|
+
* Restated rather than folded into a shared selector list, because this is
|
|
113
|
+
* plain CSS with no mixins and a `:is()` across a class and a media query is
|
|
114
|
+
* not a thing that exists. Keep the two halves in step — they are one rule.
|
|
115
|
+
* `pointer` reports the PRIMARY device, so a touchscreen laptop answers `fine`
|
|
116
|
+
* and never reaches any of this. */
|
|
117
|
+
@media (pointer: coarse) {
|
|
118
|
+
.InovuaReactDataGrid__cell.cell-word-wrap,
|
|
119
|
+
.InovuaReactDataGrid__cell.cell-word-wrap > *,
|
|
120
|
+
.InovuaReactDataGrid__cell.cell-word-wrap div,
|
|
121
|
+
.InovuaReactDataGrid__cell.cell-word-wrap span {
|
|
122
|
+
overflow: hidden !important;
|
|
123
|
+
max-height: 100% !important;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.InovuaReactDataGrid__cell.cell-word-wrap:not(
|
|
127
|
+
.InovuaReactDataGrid__cell--no-padding
|
|
128
|
+
) {
|
|
129
|
+
height: 100% !important;
|
|
130
|
+
align-items: center !important;
|
|
131
|
+
padding: var(--grid-cell-padding, 6px 8px) !important;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
135
|
+
display: -webkit-box !important;
|
|
136
|
+
-webkit-box-orient: vertical !important;
|
|
137
|
+
-webkit-line-clamp: 2 !important;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
/* Default cell behavior - no word wrap */
|
|
107
142
|
.InovuaReactDataGrid__cell:not(.cell-word-wrap) {
|
|
108
143
|
white-space: nowrap !important;
|
|
@@ -765,6 +800,63 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
765
800
|
border-radius: 6px !important;
|
|
766
801
|
background: var(--dgx-surface) !important;
|
|
767
802
|
box-shadow: none !important;
|
|
803
|
+
/* The vendored theme's padding-top: 2px — inside a hard 28px box it just
|
|
804
|
+
pushed the text off-centre. */
|
|
805
|
+
padding-top: 0 !important;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/* A narrow column must TRUNCATE the text, not run it into the arrow.
|
|
809
|
+
|
|
810
|
+
The vendored theme declares the placeholder `white-space: nowrap` and gives
|
|
811
|
+
neither it nor the flex boxes above it any way to shrink: `__value` is
|
|
812
|
+
allowed to wrap (useless inside a hard 28px height — the second line is
|
|
813
|
+
just clipped) and the input wrapper keeps its content-size width. So in a
|
|
814
|
+
column narrower than the text, "ALL OPTIONS" kept its full width and the
|
|
815
|
+
toggle arrow was painted on top of its tail.
|
|
816
|
+
|
|
817
|
+
`min-width: 0` at each flex level is what lets the text box actually give
|
|
818
|
+
up width; the ellipsis then says so honestly. The arrow is pinned
|
|
819
|
+
unshrinkable on the other side — a dropdown with no visible handle stops
|
|
820
|
+
looking like a dropdown.
|
|
821
|
+
|
|
822
|
+
The vendored 27px min-heights on `__value`/`__tools` also go: inside the
|
|
823
|
+
26px content box of the bordered 28px field they forced a 1px overflow that
|
|
824
|
+
read as the text sitting low. */
|
|
825
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box
|
|
826
|
+
.inovua-react-toolkit-combo-box__value {
|
|
827
|
+
flex-wrap: nowrap !important;
|
|
828
|
+
min-width: 0 !important;
|
|
829
|
+
min-height: 0 !important;
|
|
830
|
+
height: 100% !important;
|
|
831
|
+
overflow: hidden !important;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box
|
|
835
|
+
.inovua-react-toolkit-combo-box__input__wrapper {
|
|
836
|
+
min-width: 0 !important;
|
|
837
|
+
overflow: hidden !important;
|
|
838
|
+
margin-bottom: 0 !important;
|
|
839
|
+
/* The vendored --ltr theme rule pads THIS wrapper 8px left as well as the
|
|
840
|
+
display value — stacked on the spans' own inset that made 16px. The
|
|
841
|
+
spans carry the one true inset; the wrapper carries none. */
|
|
842
|
+
padding: 0 !important;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box
|
|
846
|
+
.inovua-react-toolkit-combo-box__input__placeholder,
|
|
847
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box
|
|
848
|
+
.inovua-react-toolkit-combo-box__value__display-value {
|
|
849
|
+
overflow: hidden !important;
|
|
850
|
+
text-overflow: ellipsis !important;
|
|
851
|
+
white-space: nowrap !important;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box
|
|
855
|
+
.inovua-react-toolkit-combo-box__tools {
|
|
856
|
+
flex: 0 0 auto !important;
|
|
857
|
+
min-height: 0 !important;
|
|
858
|
+
margin: 0 !important;
|
|
859
|
+
align-items: center !important;
|
|
768
860
|
}
|
|
769
861
|
|
|
770
862
|
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box
|
|
@@ -796,6 +888,34 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
796
888
|
padding-inline-end: 0 !important;
|
|
797
889
|
}
|
|
798
890
|
|
|
891
|
+
/* The filter row under a finger — as far as it can honestly go, which is not
|
|
892
|
+
44px.
|
|
893
|
+
*
|
|
894
|
+
* The datagrid measures its own header height in JS from the theme, which pins
|
|
895
|
+
* the filter cell to `min-height: 32px`. A 44px control inside that cell is
|
|
896
|
+
* CLIPPED, not accommodated: the grid does not re-measure because a stylesheet
|
|
897
|
+
* grew one of its children. So the touch step here is 32px — the full height
|
|
898
|
+
* of the cell the theme already reserves, up from 28 — which at least makes
|
|
899
|
+
* the whole cell the target instead of a 28px box floating inside it.
|
|
900
|
+
*
|
|
901
|
+
* The real 44px targets on a touch device are the pager (DataGrid.module.scss)
|
|
902
|
+
* and the row action buttons. Filtering a grid on a phone is a typing task
|
|
903
|
+
* with a keyboard already covering half the screen; it is not the thing this
|
|
904
|
+
* pass had to rescue. Raising it properly means changing the grid theme's
|
|
905
|
+
* header height, which moves every desktop grid too. */
|
|
906
|
+
@media (pointer: coarse) {
|
|
907
|
+
.InovuaReactDataGrid__column-header__filter input,
|
|
908
|
+
.InovuaReactDataGrid__column-header__filter select {
|
|
909
|
+
height: 32px !important;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box {
|
|
913
|
+
height: 32px !important;
|
|
914
|
+
min-height: 32px !important;
|
|
915
|
+
max-height: 32px !important;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
799
919
|
/* The keyboard highlight in a dropdown's option list — declared by the
|
|
800
920
|
vendored theme, then painted with nothing.
|
|
801
921
|
|
|
@@ -1016,3 +1136,103 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
1016
1136
|
.visns-native-date-filter__input {
|
|
1017
1137
|
font-size: 0.75rem !important;
|
|
1018
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
/* ==========================================================================
|
|
1141
|
+
NARROW VIEWPORTS — the pager, which used to be sliced off at the edge
|
|
1142
|
+
--------------------------------------------------------------------------
|
|
1143
|
+
Guarded on `(max-width: 640px)` — the same width `utils/useDensity.js`
|
|
1144
|
+
exports as NARROW_VIEWPORT_QUERY and raises the column floor at. Nothing in
|
|
1145
|
+
this block is reachable above it, so every desktop grid is untouched.
|
|
1146
|
+
|
|
1147
|
+
The pager is not the library's own markup: `pagination` on the datagrid
|
|
1148
|
+
renders the TOOLKIT's toolbar (`.inovua-react-pagination-toolbar`, no grid
|
|
1149
|
+
prefix), and that component measures itself in JS and puts its contents in
|
|
1150
|
+
an internal scroll container with little arrows at each end. On a phone its
|
|
1151
|
+
contents are 714px wide inside a 348px window, so what a reader actually
|
|
1152
|
+
gets is "|< < Page [1] of 236 > >| | R" — the "Results per page" label
|
|
1153
|
+
sheared off mid-word at the screen edge, with the rest behind a scroll
|
|
1154
|
+
affordance nobody recognises as one.
|
|
1155
|
+
|
|
1156
|
+
So the toolbar is made to WRAP instead of scroll. Wrapping keeps every
|
|
1157
|
+
control — the page size combo included — where hiding half of them would
|
|
1158
|
+
have been the easier fix and would have taken the record total off the one
|
|
1159
|
+
screen that cannot show the stat strip and the grid at the same time.
|
|
1160
|
+
|
|
1161
|
+
`!important` throughout because the widths and heights being overridden are
|
|
1162
|
+
INLINE, written by the scroll container's own measuring pass. The arrows go
|
|
1163
|
+
with the scrolling: an overflow affordance for content that no longer
|
|
1164
|
+
overflows is a button that does nothing.
|
|
1165
|
+
========================================================================== */
|
|
1166
|
+
@media (max-width: 640px) {
|
|
1167
|
+
.inovua-react-pagination-toolbar {
|
|
1168
|
+
height: auto !important;
|
|
1169
|
+
min-height: 0 !important;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.inovua-react-pagination-toolbar
|
|
1173
|
+
.inovua-react-toolkit-toolbar__scroll-container,
|
|
1174
|
+
.inovua-react-pagination-toolbar .inovua-react-scroll-container__wrapper,
|
|
1175
|
+
.inovua-react-pagination-toolbar .inovua-react-scroll-container__scroller,
|
|
1176
|
+
.inovua-react-pagination-toolbar .inovua-react-scroll-container__view {
|
|
1177
|
+
width: 100% !important;
|
|
1178
|
+
max-width: 100% !important;
|
|
1179
|
+
height: auto !important;
|
|
1180
|
+
max-height: none !important;
|
|
1181
|
+
overflow: visible !important;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.inovua-react-pagination-toolbar
|
|
1185
|
+
.inovua-react-toolkit-toolbar__inner-wrapper {
|
|
1186
|
+
flex-wrap: wrap !important;
|
|
1187
|
+
width: 100% !important;
|
|
1188
|
+
row-gap: 0.4rem;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/* The two regions and the spacer between them: the spacer is a flex
|
|
1192
|
+
`1 1 auto` that would otherwise push the record count onto a line of its
|
|
1193
|
+
own with a screen's worth of air in front of it. */
|
|
1194
|
+
.inovua-react-pagination-toolbar__region {
|
|
1195
|
+
flex-wrap: wrap !important;
|
|
1196
|
+
width: auto !important;
|
|
1197
|
+
min-width: 0 !important;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.inovua-react-pagination-toolbar__spacer {
|
|
1201
|
+
display: none !important;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/* The scroll arrows, now that nothing scrolls. */
|
|
1205
|
+
.inovua-react-pagination-toolbar .inovua-react-toolkit-toolbar__arrow {
|
|
1206
|
+
display: none !important;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/* ----------------------------------------------------------------------
|
|
1210
|
+
CARD MODE — the cell the stacked row is drawn in.
|
|
1211
|
+
|
|
1212
|
+
`datagrid-card-mode` is on the grid and `datagrid-card-cell` is on the
|
|
1213
|
+
one synthetic column; both are set in DataGrid.jsx. The cell class is
|
|
1214
|
+
what does the work, because THE CHECKBOX COLUMN IS A CELL TOO and still
|
|
1215
|
+
wants the grid's own padding and its centred alignment — a rule scoped
|
|
1216
|
+
to the grid alone would strip both and drop the tick against the edge.
|
|
1217
|
+
|
|
1218
|
+
Everything here is `!important` for the same reason the rules it
|
|
1219
|
+
overrides are: those are `!important` themselves, a few hundred lines
|
|
1220
|
+
up, and were written before this block existed.
|
|
1221
|
+
---------------------------------------------------------------------- */
|
|
1222
|
+
.datagrid-card-mode .InovuaReactDataGrid__cell.datagrid-card-cell {
|
|
1223
|
+
/* The card owns its own gutters — StackedRow's vertical padding is
|
|
1224
|
+
half the row-height arithmetic in `utils/cardLayout.js`, and two
|
|
1225
|
+
sets of it is how the last line ends up under the row below. */
|
|
1226
|
+
padding: 0 var(--spacing-sm, 0.5rem) !important;
|
|
1227
|
+
/* So `height: 100%` on the row inside resolves against the cell
|
|
1228
|
+
rather than against its own content. */
|
|
1229
|
+
align-items: stretch !important;
|
|
1230
|
+
/* The nowrap/ellipsis clamp on every non-wrapping cell would apply to
|
|
1231
|
+
the card as a whole; each LINE inside sets its own, which is what
|
|
1232
|
+
has to win. */
|
|
1233
|
+
white-space: normal !important;
|
|
1234
|
+
text-overflow: clip !important;
|
|
1235
|
+
/* A divider down the right of a full-width row is a line to nowhere. */
|
|
1236
|
+
border-right: 0 !important;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
@@ -314,6 +314,26 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
|
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
/* THE DROPDOWN ARROW, restored. The rule further up gives `select` its
|
|
318
|
+
chevron and strips the native chrome (`appearance: none`), and then the
|
|
319
|
+
chained rule above this comment writes `background: white` — a SHORTHAND,
|
|
320
|
+
which resets background-image along with the colour. Net result shipped
|
|
321
|
+
for months: every native select rendered as a plain box visually identical
|
|
322
|
+
to a text input, with nothing saying "this opens".
|
|
323
|
+
|
|
324
|
+
This sits AFTER the chained rule so source order wins at equal
|
|
325
|
+
specificity, states only the longhands the shorthand clobbered, and
|
|
326
|
+
positions the chevron inside the field (the original hugged the raw right
|
|
327
|
+
edge) with the padding to keep long option text from running under it. */
|
|
328
|
+
select {
|
|
329
|
+
background-image: url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0Ljk1IDEwIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2ZmZjt9LmNscy0ye2ZpbGw6IzQ0NDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPmFycm93czwvdGl0bGU+PHJlY3QgY2xhc3M9ImNscy0xIiB3aWR0aD0iNC45NSIgaGVpZ2h0PSIxMCIvPjxwb2x5Z29uIGNsYXNzPSJjbHMtMiIgcG9pbnRzPSIxLjQxIDQuNjcgMi40OCAzLjE4IDMuNTQgNC42NyAxLjQxIDQuNjciLz48cG9seWdvbiBjbGFzcz0iY2xzLTIiIHBvaW50cz0iMy41NCA1LjMzIDIuNDggNi44MiAxLjQxIDUuMzMgMy41NCA1LjMzIi8+PC9zdmc+);
|
|
330
|
+
background-repeat: no-repeat;
|
|
331
|
+
background-position: right 0.65rem center;
|
|
332
|
+
background-size: 7px 14px;
|
|
333
|
+
padding-right: 2rem;
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
}
|
|
336
|
+
|
|
317
337
|
/* A textarea is multi-line by definition, so it is the one control that opts
|
|
318
338
|
out of the fixed height — it keeps the font and the horizontal padding, and
|
|
319
339
|
takes the vertical padding that puts its FIRST row on the same line as the
|
|
@@ -119,6 +119,19 @@ const TicketConversation = ({
|
|
|
119
119
|
<div className={styles.wrap}>
|
|
120
120
|
{ticket && <TicketHeader ticket={ticket} counts={counts} />}
|
|
121
121
|
|
|
122
|
+
{/* The composer sits ABOVE the thread, and a posted reply is
|
|
123
|
+
PREPENDED: the server answers newest-first now, so the top of
|
|
124
|
+
the list is where the conversation currently is — writing
|
|
125
|
+
happens beside the message being answered, not three scrolls
|
|
126
|
+
below it under the original request. */}
|
|
127
|
+
{canReply && (
|
|
128
|
+
<Composer
|
|
129
|
+
base={base}
|
|
130
|
+
ticketId={ticketId}
|
|
131
|
+
onPosted={(entry) => setEntries((prev) => [entry, ...prev])}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
|
|
122
135
|
{entries.length === 0 ? (
|
|
123
136
|
<p className={styles.state}>
|
|
124
137
|
Nothing has been said on this ticket yet.
|
|
@@ -130,14 +143,6 @@ const TicketConversation = ({
|
|
|
130
143
|
))}
|
|
131
144
|
</ol>
|
|
132
145
|
)}
|
|
133
|
-
|
|
134
|
-
{canReply && (
|
|
135
|
-
<Composer
|
|
136
|
-
base={base}
|
|
137
|
-
ticketId={ticketId}
|
|
138
|
-
onPosted={(entry) => setEntries((prev) => [...prev, entry])}
|
|
139
|
-
/>
|
|
140
|
-
)}
|
|
141
146
|
</div>
|
|
142
147
|
);
|
|
143
148
|
};
|
|
@@ -42,9 +42,28 @@ export const confirmDialog = (options) => {
|
|
|
42
42
|
data = null, // Optional data object from the selected row
|
|
43
43
|
} = options;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/*
|
|
46
|
+
* WHICH BUTTON CONFIRMS IS NOT A LABEL LOOKUP ANY MORE.
|
|
47
|
+
*
|
|
48
|
+
* This used to find the confirm handler with `label === 'Yes'` and
|
|
49
|
+
* nothing else — so a dialog whose button said "Delete" rendered
|
|
50
|
+
* perfectly, showed a default "Yes" button, and ran NOTHING when it was
|
|
51
|
+
* pressed. Two real screens shipped in that state (ticket delete,
|
|
52
|
+
* customer delete): the reader confirmed a deletion and the app silently
|
|
53
|
+
* did not delete, which is the worst available failure — no error, no
|
|
54
|
+
* effect, no clue. App code even grew comments warning that every
|
|
55
|
+
* confirm button must be labelled "Yes".
|
|
56
|
+
*
|
|
57
|
+
* Now: "No" or "Cancel" (either case) is the cancel button; the FIRST
|
|
58
|
+
* button that is neither is the confirm, whatever it says — "Yes",
|
|
59
|
+
* "Delete", "Remove from this project". Its label is what the reader
|
|
60
|
+
* sees, which is how a destructive dialog should read anyway.
|
|
61
|
+
*/
|
|
62
|
+
const isCancelLabel = (label) =>
|
|
63
|
+
typeof label === 'string' && ['no', 'cancel'].includes(label.toLowerCase());
|
|
64
|
+
|
|
65
|
+
const yesButton = buttons.find((btn) => !isCancelLabel(btn.label));
|
|
66
|
+
const noButton = buttons.find((btn) => isCancelLabel(btn.label));
|
|
48
67
|
|
|
49
68
|
// Default button text
|
|
50
69
|
const confirmButtonText = yesButton ? yesButton.label : 'Yes';
|