@visns-studio/visns-components 6.31.2 → 6.32.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/README.md +34 -0
- package/package.json +1 -1
- package/src/components/generic/GenericDetail.jsx +43 -10
- package/src/components/styles/GenericDetail.module.scss +123 -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,40 @@ 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.0)
|
|
13
|
+
|
|
14
|
+
### The pre-6.15 boxed overview is back, as an opt-in
|
|
15
|
+
|
|
16
|
+
6.15.0 rebuilt the `GenericDetail` overview tab: one card per section, fields
|
|
17
|
+
as stacked label-above-value pairs on a two-column grid, the heading
|
|
18
|
+
left-aligned on a hairline. That stays the default. But a project whose users
|
|
19
|
+
read their detail pages as a grid of discrete facts — every field in its own
|
|
20
|
+
tinted, bordered box with a run-in `Label: value`, under a centred tinted
|
|
21
|
+
heading bar — can now ask for the old treatment back across the whole app,
|
|
22
|
+
the same way `setDefaultFormLayout` works:
|
|
23
|
+
|
|
24
|
+
```jsx
|
|
25
|
+
import { setDefaultOverviewStyle } from '@visns-studio/visns-components';
|
|
26
|
+
|
|
27
|
+
setDefaultOverviewStyle('boxed'); // once, at bootstrap, before the first render
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
A single page can go the other way, or opt in on its own, from its view
|
|
31
|
+
config: `"page": { "overviewStyle": "boxed" }` — or `"plain"` — wins over the
|
|
32
|
+
project default. `'plain'` is the shipped default, so an app that calls
|
|
33
|
+
nothing is completely unchanged. `getDefaultOverviewStyle()` is exported too.
|
|
34
|
+
|
|
35
|
+
### The title bar's counts no longer run together
|
|
36
|
+
|
|
37
|
+
`GenericIndex` prints each `page.tableInfo.heading` and the "N total" count as
|
|
38
|
+
its own span in the title bar. Until 6.15.0 those spans carried their own
|
|
39
|
+
spacing — `[0 Enquiry Generated Today] ` — and when the brackets were retired
|
|
40
|
+
the only space between them went with them, so a leads page read
|
|
41
|
+
"0 Enquiry Generated Today0 Enquiry with Follow Ups79 total". The tab-strip and
|
|
42
|
+
embedded rows already laid the same node out as a flex row with a 0.75rem gap;
|
|
43
|
+
the `.crmtitle` / `.crmtitleSimple` bar now does the same (wrapping when the
|
|
44
|
+
bar is narrow). CSS only; no markup or config change.
|
|
45
|
+
|
|
12
46
|
## Recent Updates (v6.31.2)
|
|
13
47
|
|
|
14
48
|
### `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.0",
|
|
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,15 @@ 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
|
+
|
|
293
303
|
/** Fileupload states */
|
|
294
304
|
const [files, setFiles] = useState([]);
|
|
295
305
|
|
|
@@ -2921,7 +2931,13 @@ function GenericDetail({
|
|
|
2921
2931
|
</div>
|
|
2922
2932
|
) : (
|
|
2923
2933
|
// Show the regular overview when not editing
|
|
2924
|
-
<div
|
|
2934
|
+
<div
|
|
2935
|
+
className={
|
|
2936
|
+
overviewIsBoxed
|
|
2937
|
+
? `${styles.gridtxt} ${styles['gridtxt--boxed']}`
|
|
2938
|
+
: styles.gridtxt
|
|
2939
|
+
}
|
|
2940
|
+
>
|
|
2925
2941
|
{activeTabConfig.sections.map(
|
|
2926
2942
|
(section, sectionIndex) => (
|
|
2927
2943
|
<React.Fragment
|
|
@@ -3095,16 +3111,33 @@ function GenericDetail({
|
|
|
3095
3111
|
liClass
|
|
3096
3112
|
}
|
|
3097
3113
|
>
|
|
3114
|
+
{/* `overviewFieldLabel` strips
|
|
3115
|
+
the trailing colon a lot of
|
|
3116
|
+
configs still carry, because
|
|
3117
|
+
stacked above the value it is
|
|
3118
|
+
noise. Boxed fields read as a
|
|
3119
|
+
run-in "Label: value", so the
|
|
3120
|
+
colon is put back here — from
|
|
3121
|
+
one place, whatever the config
|
|
3122
|
+
wrote. */}
|
|
3098
3123
|
{item.label && (
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3124
|
+
<>
|
|
3125
|
+
<strong
|
|
3126
|
+
className={
|
|
3127
|
+
styles.overviewLabel
|
|
3128
|
+
}
|
|
3129
|
+
>
|
|
3130
|
+
{overviewFieldLabel(
|
|
3131
|
+
item.label
|
|
3132
|
+
)}
|
|
3133
|
+
{overviewIsBoxed
|
|
3134
|
+
? ':'
|
|
3135
|
+
: ''}
|
|
3136
|
+
</strong>
|
|
3137
|
+
{overviewIsBoxed
|
|
3138
|
+
? ' '
|
|
3139
|
+
: null}
|
|
3140
|
+
</>
|
|
3108
3141
|
)}
|
|
3109
3142
|
<span
|
|
3110
3143
|
className={
|
|
@@ -473,6 +473,129 @@
|
|
|
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
|
+
Specificity is doing real work here. The rules being undone are
|
|
489
|
+
`.gridtxt > ul.customer__overview li` above AND the bare `.overviewLabel` /
|
|
490
|
+
`.gridtxt__header` in the 2026 pass at the bottom of the file, which win on
|
|
491
|
+
source order — so every selector below doubles up the class
|
|
492
|
+
(`.gridtxt.gridtxt--boxed …`) or qualifies the target with the modifier,
|
|
493
|
+
and nothing here relies on being later in the file.
|
|
494
|
+
|
|
495
|
+
Token fallbacks are literal for the same reason they are everywhere else in
|
|
496
|
+
this file: a bare var() for a token the host has not declared is invalid at
|
|
497
|
+
computed-value time and the declaration is simply dropped. */
|
|
498
|
+
|
|
499
|
+
/* The heading, back to a centred tinted bar. `display: flex` is kept so a
|
|
500
|
+
section's optional `.gridtxt__meta` still renders; it is centred with the
|
|
501
|
+
title rather than pushed to the far edge, since there is no edge to push to
|
|
502
|
+
once the bar is tinted. */
|
|
503
|
+
.gridtxt.gridtxt--boxed > .gridtxt__header {
|
|
504
|
+
width: 100%;
|
|
505
|
+
display: flex;
|
|
506
|
+
align-items: baseline;
|
|
507
|
+
justify-content: center;
|
|
508
|
+
gap: 0;
|
|
509
|
+
flex-wrap: wrap;
|
|
510
|
+
box-sizing: border-box;
|
|
511
|
+
padding: 10px 20px;
|
|
512
|
+
border-bottom: 0;
|
|
513
|
+
border-radius: var(--br, 5px);
|
|
514
|
+
margin-bottom: 0;
|
|
515
|
+
background: rgba(var(--primary-rgb, 27, 57, 51), 0.05);
|
|
516
|
+
font-size: var(--font-size-md, 0.9rem);
|
|
517
|
+
font-weight: 700;
|
|
518
|
+
letter-spacing: 0;
|
|
519
|
+
text-transform: none;
|
|
520
|
+
text-align: center;
|
|
521
|
+
color: var(--paragraph-color, #221e33);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* The meta sits beside the centred title, not opposite it, so it needs its own
|
|
525
|
+
small gap where the header's `space-between` used to provide one. */
|
|
526
|
+
.gridtxt.gridtxt--boxed > .gridtxt__header > .gridtxt__meta {
|
|
527
|
+
margin-left: var(--spacing-sm, 0.5rem);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* A wrapping row of boxes, not a two-column grid: a full-width field can then
|
|
531
|
+
sit between two half-width ones without leaving a hole in a grid track. */
|
|
532
|
+
.gridtxt.gridtxt--boxed > ul.customer__overview {
|
|
533
|
+
width: 100%;
|
|
534
|
+
display: flex;
|
|
535
|
+
justify-content: flex-start;
|
|
536
|
+
flex-direction: row;
|
|
537
|
+
flex-wrap: wrap;
|
|
538
|
+
gap: 0;
|
|
539
|
+
list-style: none;
|
|
540
|
+
box-sizing: border-box;
|
|
541
|
+
padding: 0.85rem;
|
|
542
|
+
margin: 0;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* The box itself. `display: block` is the whole point — the label and the
|
|
546
|
+
value are inline text in one paragraph, not two stacked rows. */
|
|
547
|
+
.gridtxt.gridtxt--boxed > ul.customer__overview li {
|
|
548
|
+
width: auto;
|
|
549
|
+
flex: 0 0 calc(50% - 10px);
|
|
550
|
+
min-width: 0;
|
|
551
|
+
display: block;
|
|
552
|
+
box-sizing: border-box;
|
|
553
|
+
padding: 0.65rem;
|
|
554
|
+
margin: 3px;
|
|
555
|
+
border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.095);
|
|
556
|
+
border-radius: var(--br, 5px);
|
|
557
|
+
background: rgba(var(--primary-rgb, 27, 57, 51), 0.015);
|
|
558
|
+
color: var(--paragraph-color, #221e33);
|
|
559
|
+
font-size: 0.85rem;
|
|
560
|
+
line-height: 1.45;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.gridtxt.gridtxt--boxed > ul.customer__overview .fw-grid-item {
|
|
564
|
+
flex: 0 0 calc(100% - 10px);
|
|
565
|
+
grid-column: auto;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* The accent on a note-shaped field, as it was before 6.15. */
|
|
569
|
+
.gridtxt.gridtxt--boxed > ul.customer__overview .notecolor {
|
|
570
|
+
border: 1px solid var(--secondary-color, #b23c3c);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* Run-in label: ordinary bold text with a colon after it, not the eyebrow the
|
|
574
|
+
stacked layout wears. */
|
|
575
|
+
.gridtxt--boxed .overviewLabel {
|
|
576
|
+
display: inline;
|
|
577
|
+
font-size: 0.85rem;
|
|
578
|
+
font-weight: 700;
|
|
579
|
+
letter-spacing: 0;
|
|
580
|
+
text-transform: none;
|
|
581
|
+
line-height: inherit;
|
|
582
|
+
color: var(--paragraph-color, #221e33);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.gridtxt--boxed .overviewValue {
|
|
586
|
+
display: inline;
|
|
587
|
+
font-size: 0.85rem;
|
|
588
|
+
line-height: inherit;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/* One column on a phone — half of a narrow screen is not a readable box. */
|
|
592
|
+
@media (max-width: 759px) {
|
|
593
|
+
.gridtxt.gridtxt--boxed > ul.customer__overview li,
|
|
594
|
+
.gridtxt.gridtxt--boxed > ul.customer__overview .fw-grid-item {
|
|
595
|
+
flex: 0 0 calc(100% - 10px);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
476
599
|
.progress {
|
|
477
600
|
width: 100%;
|
|
478
601
|
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);
|