@visns-studio/visns-components 6.31.2 → 6.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/package.json +1 -1
- package/src/components/generic/GenericDetail.jsx +62 -12
- package/src/components/styles/GenericDetail.module.scss +166 -0
- package/src/components/styles/GenericIndex.module.scss +10 -0
- package/src/index.js +11 -0
- package/src/utils/overviewStyle.js +63 -0
package/README.md
CHANGED
|
@@ -9,6 +9,53 @@ A comprehensive React component library used by the VISNS Studio team for CRM an
|
|
|
9
9
|
|
|
10
10
|
VISNS Components is a React-based UI component library that provides a set of reusable, consistent, and customizable components for building web applications. It includes components for authentication, data grids, forms, navigation, and more, designed to work seamlessly together.
|
|
11
11
|
|
|
12
|
+
## Recent Updates (v6.32.1)
|
|
13
|
+
|
|
14
|
+
### Boxed overview: one card per section, no note accent
|
|
15
|
+
|
|
16
|
+
Follow-up to 6.32.0's opt-in boxed overview. The tinted heading bar alone did
|
|
17
|
+
not separate sections once every field was a box, so in boxed mode each
|
|
18
|
+
section is now its own bordered card with its heading as the card's header
|
|
19
|
+
strip — Transport and PPE Requirement read as two things rather than one long
|
|
20
|
+
run of boxes. Note-shaped fields (Description, Comments) are ordinary fields
|
|
21
|
+
here too: the same neutral border as every other box, with no accent colour.
|
|
22
|
+
Plain mode renders byte-identical markup; nothing changes for apps that did
|
|
23
|
+
not opt in.
|
|
24
|
+
|
|
25
|
+
## Recent Updates (v6.32.0)
|
|
26
|
+
|
|
27
|
+
### The pre-6.15 boxed overview is back, as an opt-in
|
|
28
|
+
|
|
29
|
+
6.15.0 rebuilt the `GenericDetail` overview tab: one card per section, fields
|
|
30
|
+
as stacked label-above-value pairs on a two-column grid, the heading
|
|
31
|
+
left-aligned on a hairline. That stays the default. But a project whose users
|
|
32
|
+
read their detail pages as a grid of discrete facts — every field in its own
|
|
33
|
+
tinted, bordered box with a run-in `Label: value`, under a centred tinted
|
|
34
|
+
heading bar — can now ask for the old treatment back across the whole app,
|
|
35
|
+
the same way `setDefaultFormLayout` works:
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
import { setDefaultOverviewStyle } from '@visns-studio/visns-components';
|
|
39
|
+
|
|
40
|
+
setDefaultOverviewStyle('boxed'); // once, at bootstrap, before the first render
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A single page can go the other way, or opt in on its own, from its view
|
|
44
|
+
config: `"page": { "overviewStyle": "boxed" }` — or `"plain"` — wins over the
|
|
45
|
+
project default. `'plain'` is the shipped default, so an app that calls
|
|
46
|
+
nothing is completely unchanged. `getDefaultOverviewStyle()` is exported too.
|
|
47
|
+
|
|
48
|
+
### The title bar's counts no longer run together
|
|
49
|
+
|
|
50
|
+
`GenericIndex` prints each `page.tableInfo.heading` and the "N total" count as
|
|
51
|
+
its own span in the title bar. Until 6.15.0 those spans carried their own
|
|
52
|
+
spacing — `[0 Enquiry Generated Today] ` — and when the brackets were retired
|
|
53
|
+
the only space between them went with them, so a leads page read
|
|
54
|
+
"0 Enquiry Generated Today0 Enquiry with Follow Ups79 total". The tab-strip and
|
|
55
|
+
embedded rows already laid the same node out as a flex row with a 0.75rem gap;
|
|
56
|
+
the `.crmtitle` / `.crmtitleSimple` bar now does the same (wrapping when the
|
|
57
|
+
bar is narrow). CSS only; no markup or config change.
|
|
58
|
+
|
|
12
59
|
## Recent Updates (v6.31.2)
|
|
13
60
|
|
|
14
61
|
### `MergeEntity` compares the two records it is actually merging
|
package/package.json
CHANGED
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
94
94
|
},
|
|
95
95
|
"name": "@visns-studio/visns-components",
|
|
96
|
-
"version": "6.
|
|
96
|
+
"version": "6.32.1",
|
|
97
97
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
98
98
|
"main": "src/index.js",
|
|
99
99
|
"files": [
|
|
@@ -48,6 +48,7 @@ import { toast } from 'react-toastify';
|
|
|
48
48
|
import imageCompression from 'browser-image-compression';
|
|
49
49
|
import fetchUtil from '../../utils/fetchUtil';
|
|
50
50
|
import { resolveFormVariant } from '../../utils/formLayout';
|
|
51
|
+
import { resolveOverviewStyle } from '../../utils/overviewStyle';
|
|
51
52
|
import {
|
|
52
53
|
CheckCircle as CircleCheck,
|
|
53
54
|
Copy,
|
|
@@ -290,6 +291,31 @@ function GenericDetail({
|
|
|
290
291
|
const actionPlacement = resolveEditPlacement(setting, editPlacement);
|
|
291
292
|
const drawActionsInHeader = isHeaderPlacement(actionPlacement);
|
|
292
293
|
|
|
294
|
+
/**
|
|
295
|
+
* `'plain'` (the 6.15 card) unless this page's config asks for the older
|
|
296
|
+
* boxed fields, or the app set `setDefaultOverviewStyle('boxed')` at
|
|
297
|
+
* bootstrap. Resolved once per render so every section of the overview
|
|
298
|
+
* draws the same way.
|
|
299
|
+
*/
|
|
300
|
+
const overviewStyle = resolveOverviewStyle(page?.overviewStyle);
|
|
301
|
+
const overviewIsBoxed = overviewStyle === 'boxed';
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* How one overview section is wrapped.
|
|
305
|
+
*
|
|
306
|
+
* Boxed mode draws every section as its own card, so it needs a real
|
|
307
|
+
* element around the heading and the field list. Plain mode must NOT gain
|
|
308
|
+
* one: its rules are written as `.gridtxt > ul.customer__overview`, and a
|
|
309
|
+
* wrapper would stop the list being a direct child — so it keeps the
|
|
310
|
+
* `React.Fragment` it has always used and its markup is unchanged. A
|
|
311
|
+
* Fragment accepts `key` but nothing else, hence the separate props
|
|
312
|
+
* object rather than a className passed unconditionally.
|
|
313
|
+
*/
|
|
314
|
+
const SectionWrap = overviewIsBoxed ? 'section' : React.Fragment;
|
|
315
|
+
const sectionWrapProps = overviewIsBoxed
|
|
316
|
+
? { className: styles.gridtxt__section }
|
|
317
|
+
: {};
|
|
318
|
+
|
|
293
319
|
/** Fileupload states */
|
|
294
320
|
const [files, setFiles] = useState([]);
|
|
295
321
|
|
|
@@ -2921,11 +2947,18 @@ function GenericDetail({
|
|
|
2921
2947
|
</div>
|
|
2922
2948
|
) : (
|
|
2923
2949
|
// Show the regular overview when not editing
|
|
2924
|
-
<div
|
|
2950
|
+
<div
|
|
2951
|
+
className={
|
|
2952
|
+
overviewIsBoxed
|
|
2953
|
+
? `${styles.gridtxt} ${styles['gridtxt--boxed']}`
|
|
2954
|
+
: styles.gridtxt
|
|
2955
|
+
}
|
|
2956
|
+
>
|
|
2925
2957
|
{activeTabConfig.sections.map(
|
|
2926
2958
|
(section, sectionIndex) => (
|
|
2927
|
-
<
|
|
2959
|
+
<SectionWrap
|
|
2928
2960
|
key={`section-${activeTabConfig.id}-${sectionIndex}`}
|
|
2961
|
+
{...sectionWrapProps}
|
|
2929
2962
|
>
|
|
2930
2963
|
<div
|
|
2931
2964
|
className={
|
|
@@ -3095,16 +3128,33 @@ function GenericDetail({
|
|
|
3095
3128
|
liClass
|
|
3096
3129
|
}
|
|
3097
3130
|
>
|
|
3131
|
+
{/* `overviewFieldLabel` strips
|
|
3132
|
+
the trailing colon a lot of
|
|
3133
|
+
configs still carry, because
|
|
3134
|
+
stacked above the value it is
|
|
3135
|
+
noise. Boxed fields read as a
|
|
3136
|
+
run-in "Label: value", so the
|
|
3137
|
+
colon is put back here — from
|
|
3138
|
+
one place, whatever the config
|
|
3139
|
+
wrote. */}
|
|
3098
3140
|
{item.label && (
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3141
|
+
<>
|
|
3142
|
+
<strong
|
|
3143
|
+
className={
|
|
3144
|
+
styles.overviewLabel
|
|
3145
|
+
}
|
|
3146
|
+
>
|
|
3147
|
+
{overviewFieldLabel(
|
|
3148
|
+
item.label
|
|
3149
|
+
)}
|
|
3150
|
+
{overviewIsBoxed
|
|
3151
|
+
? ':'
|
|
3152
|
+
: ''}
|
|
3153
|
+
</strong>
|
|
3154
|
+
{overviewIsBoxed
|
|
3155
|
+
? ' '
|
|
3156
|
+
: null}
|
|
3157
|
+
</>
|
|
3108
3158
|
)}
|
|
3109
3159
|
<span
|
|
3110
3160
|
className={
|
|
@@ -3121,7 +3171,7 @@ function GenericDetail({
|
|
|
3121
3171
|
)}
|
|
3122
3172
|
</ul>
|
|
3123
3173
|
)}
|
|
3124
|
-
</
|
|
3174
|
+
</SectionWrap>
|
|
3125
3175
|
)
|
|
3126
3176
|
)}
|
|
3127
3177
|
</div>
|
|
@@ -473,6 +473,172 @@
|
|
|
473
473
|
color: var(--muted-color, rgba(var(--paragraph-color-rgb, 34, 30, 51), 0.45));
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
/* ==========================================================================
|
|
477
|
+
The boxed overview — opt-in, and only opt-in.
|
|
478
|
+
--------------------------------------------------------------------------
|
|
479
|
+
The 6.15 rework above is the default and stays the default. But one team
|
|
480
|
+
reads their detail pages as a grid of discrete facts, not as a card of
|
|
481
|
+
pairs, and asked for the pre-6.15 treatment back: every field in its own
|
|
482
|
+
tinted, bordered box with a run-in "Label: value", under a centred tinted
|
|
483
|
+
heading bar. Rather than fork the component, an app calls
|
|
484
|
+
`setDefaultOverviewStyle('boxed')` at bootstrap (or a single page sets
|
|
485
|
+
`page.overviewStyle`), GenericDetail adds `.gridtxt--boxed`, and these
|
|
486
|
+
rules put the 5.26 geometry back.
|
|
487
|
+
|
|
488
|
+
Each section is also its own card here, wrapped in `.gridtxt__section`.
|
|
489
|
+
The tinted heading bar on its own was enough to separate sections while the
|
|
490
|
+
fields below it were plain stacked pairs, but once every field is a tinted,
|
|
491
|
+
bordered box the bar reads as one more box in the run: bar, boxes, bar,
|
|
492
|
+
boxes, all inside a single panel, with nothing saying where Transport ends
|
|
493
|
+
and PPE Requirement begins. So the section gets a bordered card and the
|
|
494
|
+
heading becomes that card's header strip — square, full-bleed, hairline
|
|
495
|
+
underneath. Plain mode keeps emitting the header and the `<ul>` as direct
|
|
496
|
+
children of `.gridtxt` (the rules above depend on it), so every selector
|
|
497
|
+
below reaches through `> .gridtxt__section >` and only boxed markup has
|
|
498
|
+
one.
|
|
499
|
+
|
|
500
|
+
One thing from 5.26 is deliberately NOT restored: the red accent on a
|
|
501
|
+
`.notecolor` field. Description and Comments are ordinary fields here too,
|
|
502
|
+
and the brand red read as an error on a page with nothing wrong — the same
|
|
503
|
+
reasoning that removed it from the plain overview. There is no boxed
|
|
504
|
+
`.notecolor` rule below because there does not need to be: the generic
|
|
505
|
+
boxed `li` rule is `.gridtxt.gridtxt--boxed > .gridtxt__section >
|
|
506
|
+
ul.customer__overview li` (0,4,2) and the plain accent it has to beat is
|
|
507
|
+
`.gridtxt > ul .notecolor` (0,2,1), so a note field simply takes the same
|
|
508
|
+
neutral border and tint as every other box.
|
|
509
|
+
|
|
510
|
+
Specificity is doing real work here. The rules being undone are
|
|
511
|
+
`.gridtxt > ul.customer__overview li` above AND the bare `.overviewLabel` /
|
|
512
|
+
`.gridtxt__header` in the 2026 pass at the bottom of the file, which win on
|
|
513
|
+
source order — so every selector below doubles up the class
|
|
514
|
+
(`.gridtxt.gridtxt--boxed …`) or qualifies the target with the modifier,
|
|
515
|
+
and nothing here relies on being later in the file.
|
|
516
|
+
|
|
517
|
+
Token fallbacks are literal for the same reason they are everywhere else in
|
|
518
|
+
this file: a bare var() for a token the host has not declared is invalid at
|
|
519
|
+
computed-value time and the declaration is simply dropped. */
|
|
520
|
+
|
|
521
|
+
/* The panel's own inset is dialled back once the sections are cards: the tab
|
|
522
|
+
card already pads, and `.gridtxt`'s 1rem/1.5rem on top of a card border on
|
|
523
|
+
top of the `<ul>`'s 0.85rem stacked three insets between the panel edge and
|
|
524
|
+
the first field. Small but not zero — the cards still need to sit off the
|
|
525
|
+
panel border. */
|
|
526
|
+
.gridtxt.gridtxt--boxed {
|
|
527
|
+
padding: var(--spacing-sm, 0.5rem);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* One section, one card. `overflow: hidden` is what lets the square heading
|
|
531
|
+
strip below sit flush inside the rounded corners. */
|
|
532
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section {
|
|
533
|
+
display: block;
|
|
534
|
+
box-sizing: border-box;
|
|
535
|
+
background: var(--surface-color, #fff);
|
|
536
|
+
border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.12);
|
|
537
|
+
border-radius: var(--br, 5px);
|
|
538
|
+
overflow: hidden;
|
|
539
|
+
margin: 0 0 var(--spacing-md, 1rem);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section:last-child {
|
|
543
|
+
margin-bottom: 0;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* The heading, back to a centred tinted bar — and now the card's header
|
|
547
|
+
strip, so it runs the full width with square corners and a hairline under
|
|
548
|
+
it. `display: flex` is kept so a section's optional `.gridtxt__meta` still
|
|
549
|
+
renders; it is centred with the title rather than pushed to the far edge,
|
|
550
|
+
since there is no edge to push to once the bar is tinted. */
|
|
551
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > .gridtxt__header {
|
|
552
|
+
width: 100%;
|
|
553
|
+
display: flex;
|
|
554
|
+
align-items: baseline;
|
|
555
|
+
justify-content: center;
|
|
556
|
+
gap: 0;
|
|
557
|
+
flex-wrap: wrap;
|
|
558
|
+
box-sizing: border-box;
|
|
559
|
+
padding: 10px 20px;
|
|
560
|
+
border-bottom: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.08);
|
|
561
|
+
border-radius: 0;
|
|
562
|
+
margin-bottom: 0;
|
|
563
|
+
background: rgba(var(--primary-rgb, 27, 57, 51), 0.05);
|
|
564
|
+
font-size: var(--font-size-md, 0.9rem);
|
|
565
|
+
font-weight: 700;
|
|
566
|
+
letter-spacing: 0;
|
|
567
|
+
text-transform: none;
|
|
568
|
+
text-align: center;
|
|
569
|
+
color: var(--paragraph-color, #221e33);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/* The meta sits beside the centred title, not opposite it, so it needs its own
|
|
573
|
+
small gap where the header's `space-between` used to provide one. */
|
|
574
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > .gridtxt__header > .gridtxt__meta {
|
|
575
|
+
margin-left: var(--spacing-sm, 0.5rem);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* A wrapping row of boxes, not a two-column grid: a full-width field can then
|
|
579
|
+
sit between two half-width ones without leaving a hole in a grid track. */
|
|
580
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > ul.customer__overview {
|
|
581
|
+
width: 100%;
|
|
582
|
+
display: flex;
|
|
583
|
+
justify-content: flex-start;
|
|
584
|
+
flex-direction: row;
|
|
585
|
+
flex-wrap: wrap;
|
|
586
|
+
gap: 0;
|
|
587
|
+
list-style: none;
|
|
588
|
+
box-sizing: border-box;
|
|
589
|
+
padding: 0.85rem;
|
|
590
|
+
margin: 0;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/* The box itself. `display: block` is the whole point — the label and the
|
|
594
|
+
value are inline text in one paragraph, not two stacked rows. */
|
|
595
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > ul.customer__overview li {
|
|
596
|
+
width: auto;
|
|
597
|
+
flex: 0 0 calc(50% - 10px);
|
|
598
|
+
min-width: 0;
|
|
599
|
+
display: block;
|
|
600
|
+
box-sizing: border-box;
|
|
601
|
+
padding: 0.65rem;
|
|
602
|
+
margin: 3px;
|
|
603
|
+
border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.095);
|
|
604
|
+
border-radius: var(--br, 5px);
|
|
605
|
+
background: rgba(var(--primary-rgb, 27, 57, 51), 0.015);
|
|
606
|
+
color: var(--paragraph-color, #221e33);
|
|
607
|
+
font-size: 0.85rem;
|
|
608
|
+
line-height: 1.45;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > ul.customer__overview .fw-grid-item {
|
|
612
|
+
flex: 0 0 calc(100% - 10px);
|
|
613
|
+
grid-column: auto;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Run-in label: ordinary bold text with a colon after it, not the eyebrow the
|
|
617
|
+
stacked layout wears. */
|
|
618
|
+
.gridtxt--boxed .overviewLabel {
|
|
619
|
+
display: inline;
|
|
620
|
+
font-size: 0.85rem;
|
|
621
|
+
font-weight: 700;
|
|
622
|
+
letter-spacing: 0;
|
|
623
|
+
text-transform: none;
|
|
624
|
+
line-height: inherit;
|
|
625
|
+
color: var(--paragraph-color, #221e33);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.gridtxt--boxed .overviewValue {
|
|
629
|
+
display: inline;
|
|
630
|
+
font-size: 0.85rem;
|
|
631
|
+
line-height: inherit;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* One column on a phone — half of a narrow screen is not a readable box. */
|
|
635
|
+
@media (max-width: 759px) {
|
|
636
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > ul.customer__overview li,
|
|
637
|
+
.gridtxt.gridtxt--boxed > .gridtxt__section > ul.customer__overview .fw-grid-item {
|
|
638
|
+
flex: 0 0 calc(100% - 10px);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
476
642
|
.progress {
|
|
477
643
|
width: 100%;
|
|
478
644
|
display: block;
|
|
@@ -881,6 +881,16 @@
|
|
|
881
881
|
position: static;
|
|
882
882
|
width: max-content;
|
|
883
883
|
flex: 0 0 auto;
|
|
884
|
+
/* Each `tableInfo` heading and the "N total" count is its own span.
|
|
885
|
+
Until 6.15.0 they were spaced by their own text — "[0 Today] " —
|
|
886
|
+
and when the brackets went, so did the only space between them,
|
|
887
|
+
so "0 Today0 Follow Ups79 total" ran together. The tab-strip and
|
|
888
|
+
embedded rows below already lay the same node out as a flex row;
|
|
889
|
+
the title bar now does too. */
|
|
890
|
+
display: flex;
|
|
891
|
+
align-items: baseline;
|
|
892
|
+
flex-wrap: wrap;
|
|
893
|
+
gap: 0.75rem;
|
|
884
894
|
}
|
|
885
895
|
|
|
886
896
|
.titleInfo span {
|
package/src/index.js
CHANGED
|
@@ -22,6 +22,14 @@ import {
|
|
|
22
22
|
getDefaultFormLayout,
|
|
23
23
|
resolveFormVariant,
|
|
24
24
|
} from './utils/formLayout';
|
|
25
|
+
// Project-wide default for how a detail page's overview fields are drawn — the
|
|
26
|
+
// 6.15 card unless an app calls setDefaultOverviewStyle('boxed') at bootstrap.
|
|
27
|
+
// A page config's own `page.overviewStyle` still wins.
|
|
28
|
+
import {
|
|
29
|
+
setDefaultOverviewStyle,
|
|
30
|
+
getDefaultOverviewStyle,
|
|
31
|
+
resolveOverviewStyle,
|
|
32
|
+
} from './utils/overviewStyle';
|
|
25
33
|
|
|
26
34
|
/** CRM Components */
|
|
27
35
|
import AsyncSelect from './components/AsyncSelect';
|
|
@@ -550,6 +558,7 @@ export {
|
|
|
550
558
|
GenericReport,
|
|
551
559
|
GenericSort,
|
|
552
560
|
getDefaultFormLayout,
|
|
561
|
+
getDefaultOverviewStyle,
|
|
553
562
|
GroupedReportRenderer,
|
|
554
563
|
hasChallengeState,
|
|
555
564
|
ImpersonateGate,
|
|
@@ -598,6 +607,7 @@ export {
|
|
|
598
607
|
resolveEditPlacement,
|
|
599
608
|
isHeaderPlacement,
|
|
600
609
|
resolveFormVariant,
|
|
610
|
+
resolveOverviewStyle,
|
|
601
611
|
// Passkeys (WebAuthn). Exported so a consuming app can build its own
|
|
602
612
|
// enrolment screen against the same marshalling the login screen uses.
|
|
603
613
|
base64UrlToBytes,
|
|
@@ -612,6 +622,7 @@ export {
|
|
|
612
622
|
SelectList,
|
|
613
623
|
Select,
|
|
614
624
|
setDefaultFormLayout,
|
|
625
|
+
setDefaultOverviewStyle,
|
|
615
626
|
showConfirmDialog,
|
|
616
627
|
SortableList,
|
|
617
628
|
StagePopupModal,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-wide default for how a detail page's overview fields are drawn.
|
|
3
|
+
*
|
|
4
|
+
* GenericDetail's overview tab can draw a section either as the 6.15 card
|
|
5
|
+
* (`'plain'` — one panel, fields as stacked label-above-value pairs on a
|
|
6
|
+
* two-column grid, a left-aligned heading on a hairline) or as the pre-6.15
|
|
7
|
+
* boxes (`'boxed'` — every field in its own tinted, bordered box with the
|
|
8
|
+
* label inline as "Label: value", under a centred tinted heading bar). The
|
|
9
|
+
* library ships with `'plain'` as its default so existing projects keep the
|
|
10
|
+
* presentation they already have. An app that wants the older, boxier reading
|
|
11
|
+
* calls `setDefaultOverviewStyle('boxed')` once at bootstrap (before the first
|
|
12
|
+
* render) and every GenericDetail overview follows.
|
|
13
|
+
*
|
|
14
|
+
* An individual page config still wins in both directions: set
|
|
15
|
+
* `"page": { "overviewStyle": "boxed" }` — or `'plain'` — on the view and that
|
|
16
|
+
* page ignores the project default.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The two presentations the overview understands. */
|
|
20
|
+
const VALID_STYLES = ['plain', 'boxed'];
|
|
21
|
+
|
|
22
|
+
/** Library default — the 6.15 card, unless a project opts out. */
|
|
23
|
+
let defaultOverviewStyle = 'plain';
|
|
24
|
+
|
|
25
|
+
/** Warn at most once so a bad bootstrap call is not a console flood. */
|
|
26
|
+
let warnedInvalidStyle = false;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Set the project-wide default presentation for overview fields.
|
|
30
|
+
* @param {'plain'|'boxed'} style - Presentation to use when a page config does
|
|
31
|
+
* not specify its own `page.overviewStyle`.
|
|
32
|
+
* @returns {void} Anything other than 'plain' or 'boxed' is ignored.
|
|
33
|
+
*/
|
|
34
|
+
export const setDefaultOverviewStyle = (style) => {
|
|
35
|
+
if (!VALID_STYLES.includes(style)) {
|
|
36
|
+
if (!warnedInvalidStyle) {
|
|
37
|
+
warnedInvalidStyle = true;
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.warn(
|
|
40
|
+
`setDefaultOverviewStyle: expected 'plain' or 'boxed', received ${JSON.stringify(style)}. Keeping '${defaultOverviewStyle}'.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
defaultOverviewStyle = style;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read the current project-wide default.
|
|
51
|
+
* @returns {'plain'|'boxed'} The default presentation for overview fields.
|
|
52
|
+
*/
|
|
53
|
+
export const getDefaultOverviewStyle = () => defaultOverviewStyle;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the overview presentation for one detail page.
|
|
57
|
+
* @param {string} [style] - The page config's own `page.overviewStyle`, if it
|
|
58
|
+
* has one.
|
|
59
|
+
* @returns {'plain'|'boxed'} The page's own style when it is valid, otherwise
|
|
60
|
+
* the project-wide default.
|
|
61
|
+
*/
|
|
62
|
+
export const resolveOverviewStyle = (style) =>
|
|
63
|
+
(VALID_STYLES.includes(style) ? style : defaultOverviewStyle);
|