@visns-studio/visns-components 6.4.3 → 6.5.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 +1 -1
- package/src/components/DataGrid.jsx +16 -1
- package/src/components/Field.jsx +21 -21
- package/src/components/Navigation.jsx +201 -41
- package/src/components/columns/NativeDateFilterEditor.jsx +434 -0
- package/src/components/styles/Field.module.scss +25 -3
- package/src/components/styles/Navigation.module.scss +184 -0
- package/src/components/styles/Notification.module.scss +83 -0
- package/src/components/styles/SwitchAccount.module.scss +22 -0
- package/src/components/styles/global-datagrid.css +141 -0
- package/src/components/styles/global.css +3 -1
- package/src/index.js +2 -0
package/package.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
92
92
|
},
|
|
93
93
|
"name": "@visns-studio/visns-components",
|
|
94
|
-
"version": "6.
|
|
94
|
+
"version": "6.5.0",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -39,6 +39,7 @@ import NumberFilter from '@visns-studio/visns-datagrid-enterprise/NumberFilter';
|
|
|
39
39
|
import SelectFilter from '@visns-studio/visns-datagrid-enterprise/SelectFilter';
|
|
40
40
|
import DateFilter from '@visns-studio/visns-datagrid-enterprise/DateFilter';
|
|
41
41
|
import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
|
|
42
|
+
import NativeDateFilterEditor from './columns/NativeDateFilterEditor';
|
|
42
43
|
import { toast } from 'react-toastify';
|
|
43
44
|
import fetchUtil from '../utils/fetchUtil';
|
|
44
45
|
import { confirmDialog } from './utils/ConfirmDialog';
|
|
@@ -3725,7 +3726,21 @@ const DataGrid = forwardRef(
|
|
|
3725
3726
|
if (column.filter && column.filter.type) {
|
|
3726
3727
|
switch (column.filter.type) {
|
|
3727
3728
|
case 'date':
|
|
3728
|
-
|
|
3729
|
+
// Native `<input type="date">` by default
|
|
3730
|
+
// — the popup calendar is clipped inside a
|
|
3731
|
+
// scrolling grid and its day cells are far
|
|
3732
|
+
// below a comfortable tap target. A column
|
|
3733
|
+
// can opt back into it with
|
|
3734
|
+
// `filter: { datePicker: 'popup' }`, the
|
|
3735
|
+
// same escape hatch Field uses. Both
|
|
3736
|
+
// editors emit the identical value
|
|
3737
|
+
// (`dateFormat`-formatted string, `''`
|
|
3738
|
+
// when cleared), so the posted `where[]`
|
|
3739
|
+
// entry is unchanged either way.
|
|
3740
|
+
filterEditor =
|
|
3741
|
+
column.filter.datePicker === 'popup'
|
|
3742
|
+
? DateFilter
|
|
3743
|
+
: NativeDateFilterEditor;
|
|
3729
3744
|
filterEditorProps = (props, { index }) => {
|
|
3730
3745
|
return {
|
|
3731
3746
|
dateFormat: 'DD-MM-YYYY',
|
package/src/components/Field.jsx
CHANGED
|
@@ -45,7 +45,6 @@ import DatePickerPortal from './utils/DatePickerPortal';
|
|
|
45
45
|
import {
|
|
46
46
|
fromNativeInputValue,
|
|
47
47
|
toNativeInputValue,
|
|
48
|
-
useNativeDateInput,
|
|
49
48
|
} from './utils/nativeDateInput';
|
|
50
49
|
|
|
51
50
|
import 'react-toggle/style.css';
|
|
@@ -171,15 +170,6 @@ function Field({
|
|
|
171
170
|
const [emailSuggestions, setEmailSuggestions] = useState([]);
|
|
172
171
|
const [showEmailSuggestions, setShowEmailSuggestions] = useState(false);
|
|
173
172
|
|
|
174
|
-
/**
|
|
175
|
-
* On touch / narrow devices `date` and `datetime` fields swap
|
|
176
|
-
* react-datepicker's popup calendar for the platform's own picker — the
|
|
177
|
-
* popup is clipped by modals on a phone and its day cells are far below a
|
|
178
|
-
* comfortable tap target. Desktop rendering is untouched.
|
|
179
|
-
* See utils/nativeDateInput.js for the (identical) value contract.
|
|
180
|
-
*/
|
|
181
|
-
const nativeDateInput = useNativeDateInput();
|
|
182
|
-
|
|
183
173
|
/**
|
|
184
174
|
* `colour` field popup picker: dismiss the popover when the user clicks
|
|
185
175
|
* anywhere outside it or presses Escape. Picking a colour inside the
|
|
@@ -3147,16 +3137,22 @@ function Field({
|
|
|
3147
3137
|
}
|
|
3148
3138
|
autoComplete="off"
|
|
3149
3139
|
/>
|
|
3150
|
-
{
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3140
|
+
{/* Always mounted: inserting this node while the input is
|
|
3141
|
+
focused makes mobile browsers dismiss the just-opened
|
|
3142
|
+
native picker (the DOM mutated around the focused
|
|
3143
|
+
control). Visibility is CSS-only. */}
|
|
3144
|
+
<button
|
|
3145
|
+
type="button"
|
|
3146
|
+
aria-label="Clear date"
|
|
3147
|
+
aria-hidden={value ? undefined : 'true'}
|
|
3148
|
+
tabIndex={value ? 0 : -1}
|
|
3149
|
+
className={`${styles.nativeDateClear} ${
|
|
3150
|
+
value ? '' : styles.nativeDateClearHidden
|
|
3151
|
+
}`}
|
|
3152
|
+
onClick={() => onChangeDate(null, settings.id)}
|
|
3153
|
+
>
|
|
3154
|
+
<X size={16} />
|
|
3155
|
+
</button>
|
|
3160
3156
|
</div>
|
|
3161
3157
|
);
|
|
3162
3158
|
};
|
|
@@ -3165,7 +3161,11 @@ function Field({
|
|
|
3165
3161
|
switch (settings.type) {
|
|
3166
3162
|
case 'date':
|
|
3167
3163
|
case 'datetime':
|
|
3168
|
-
|
|
3164
|
+
// Native on every viewport — the popup calendar clips inside
|
|
3165
|
+
// modals and duplicates what browsers now do well themselves.
|
|
3166
|
+
// A field can opt back into the react-datepicker popup with
|
|
3167
|
+
// `datePicker: 'popup'` in its config.
|
|
3168
|
+
if (settings.datePicker !== 'popup') {
|
|
3169
3169
|
return renderNativeDateInput(settings.type === 'datetime');
|
|
3170
3170
|
}
|
|
3171
3171
|
|
|
@@ -50,6 +50,32 @@ const NAV_ICONS = {
|
|
|
50
50
|
users: Users,
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
// The icons an account action can wear, named by a settings entry's `icon`
|
|
54
|
+
// key. Module scope because two surfaces read it now: the header's action
|
|
55
|
+
// chips and — when a project opts in — the sidebar's pinned action rows.
|
|
56
|
+
const SETTING_ICONS = {
|
|
57
|
+
bug: Bug,
|
|
58
|
+
calendar: Calendar,
|
|
59
|
+
draft: FileText,
|
|
60
|
+
image: Image,
|
|
61
|
+
peopleGroup: Users,
|
|
62
|
+
person: User,
|
|
63
|
+
phone: Phone,
|
|
64
|
+
shield: Shield,
|
|
65
|
+
signOut: LogOut,
|
|
66
|
+
settings: Settings,
|
|
67
|
+
search: Search,
|
|
68
|
+
userSearch: UserSearch,
|
|
69
|
+
clipboardCheck: ClipboardCheck,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// A rail row carries a visible label. In the header these actions were icon
|
|
73
|
+
// chips with a tooltip, so a settings entry is allowed to have no label of
|
|
74
|
+
// its own — the bell in particular usually doesn't.
|
|
75
|
+
const RAIL_ACTION_LABELS = {
|
|
76
|
+
notifications: 'Notifications',
|
|
77
|
+
};
|
|
78
|
+
|
|
53
79
|
const SearchComponent = ({
|
|
54
80
|
navigate,
|
|
55
81
|
search,
|
|
@@ -270,21 +296,7 @@ function Navigation({
|
|
|
270
296
|
};
|
|
271
297
|
|
|
272
298
|
const renderSetting = (n) => {
|
|
273
|
-
const iconComponents =
|
|
274
|
-
bug: Bug,
|
|
275
|
-
calendar: Calendar,
|
|
276
|
-
draft: FileText,
|
|
277
|
-
image: Image,
|
|
278
|
-
peopleGroup: Users,
|
|
279
|
-
person: User,
|
|
280
|
-
phone: Phone,
|
|
281
|
-
shield: Shield,
|
|
282
|
-
signOut: LogOut,
|
|
283
|
-
settings: Settings,
|
|
284
|
-
search: Search,
|
|
285
|
-
userSearch: UserSearch,
|
|
286
|
-
clipboardCheck: ClipboardCheck,
|
|
287
|
-
};
|
|
299
|
+
const iconComponents = SETTING_ICONS;
|
|
288
300
|
|
|
289
301
|
// Check if the setting's URL matches the current page
|
|
290
302
|
const isActive = n.url && n.url === `/${currentPage}`;
|
|
@@ -356,6 +368,93 @@ function Navigation({
|
|
|
356
368
|
}
|
|
357
369
|
};
|
|
358
370
|
|
|
371
|
+
/**
|
|
372
|
+
* The same account actions, drawn as rows for the sidebar rail.
|
|
373
|
+
*
|
|
374
|
+
* Opt-in only — see `actionsInSidebar` below. Every action keeps the
|
|
375
|
+
* behaviour it had in the header: the same logout handler, the same
|
|
376
|
+
* links, the same mounted components (notifications, account switcher),
|
|
377
|
+
* so nothing here re-implements a chip. What changes is the shape: a
|
|
378
|
+
* full-width row wearing the rail's own language, icon then label, since
|
|
379
|
+
* a 36px chip in a 216px column reads as a stray button.
|
|
380
|
+
*
|
|
381
|
+
* Components mounted here draw their own trigger and can't be handed a
|
|
382
|
+
* label, so the row publishes one as `--nav-action-label` for their
|
|
383
|
+
* stylesheet to place. See Notification.module.scss / SwitchAccount.
|
|
384
|
+
*/
|
|
385
|
+
const renderRailSetting = (n) => {
|
|
386
|
+
const isActive = n.url && n.url === `/${currentPage}`;
|
|
387
|
+
const activeClass = isActive ? styles.active : '';
|
|
388
|
+
const label = n.label || RAIL_ACTION_LABELS[n.id] || '';
|
|
389
|
+
// A CSS string, quoted and escaped, for `content`.
|
|
390
|
+
const labelVar = { '--nav-action-label': JSON.stringify(label) };
|
|
391
|
+
const IconComponent = SETTING_ICONS[n.icon];
|
|
392
|
+
|
|
393
|
+
if (n.id === 'notifications') {
|
|
394
|
+
return (
|
|
395
|
+
<li style={labelVar}>
|
|
396
|
+
<Notification setSystemAuth={setSystemAuth} />
|
|
397
|
+
</li>
|
|
398
|
+
);
|
|
399
|
+
} else if (n.id === 'businessCardOcr') {
|
|
400
|
+
return (
|
|
401
|
+
<li>
|
|
402
|
+
<button
|
|
403
|
+
onClick={() => setShowBusinessCardOcr(true)}
|
|
404
|
+
className={activeClass}
|
|
405
|
+
type="button"
|
|
406
|
+
>
|
|
407
|
+
<CreditCard size={19} strokeWidth={2} />
|
|
408
|
+
<span>{label}</span>
|
|
409
|
+
</button>
|
|
410
|
+
</li>
|
|
411
|
+
);
|
|
412
|
+
} else if (n.id === 'switchAccount') {
|
|
413
|
+
return (
|
|
414
|
+
<li style={labelVar}>
|
|
415
|
+
<SwitchAccount
|
|
416
|
+
setSystemAuth={setSystemAuth}
|
|
417
|
+
setting={n}
|
|
418
|
+
userProfile={userProfile}
|
|
419
|
+
/>
|
|
420
|
+
</li>
|
|
421
|
+
);
|
|
422
|
+
} else if (n.id === 'logout') {
|
|
423
|
+
return (
|
|
424
|
+
<li>
|
|
425
|
+
<button
|
|
426
|
+
onClick={handleLogout}
|
|
427
|
+
className={activeClass}
|
|
428
|
+
type="button"
|
|
429
|
+
>
|
|
430
|
+
{IconComponent && (
|
|
431
|
+
<IconComponent size={19} strokeWidth={2} />
|
|
432
|
+
)}
|
|
433
|
+
<span>{label}</span>
|
|
434
|
+
</button>
|
|
435
|
+
</li>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<li>
|
|
441
|
+
<Link
|
|
442
|
+
to={n.url}
|
|
443
|
+
className={`${activeClass}`}
|
|
444
|
+
target={n.target ? n.target : '_self'}
|
|
445
|
+
// The rail is the mobile drawer below 1024px; navigating
|
|
446
|
+
// from it has to close the drawer, as the nav list does.
|
|
447
|
+
onClick={() => setOpen(false)}
|
|
448
|
+
>
|
|
449
|
+
{IconComponent && (
|
|
450
|
+
<IconComponent size={19} strokeWidth={2} />
|
|
451
|
+
)}
|
|
452
|
+
<span>{label}</span>
|
|
453
|
+
</Link>
|
|
454
|
+
</li>
|
|
455
|
+
);
|
|
456
|
+
};
|
|
457
|
+
|
|
359
458
|
const [, setActiveDropdown] = useState(null); // We only need the setter
|
|
360
459
|
const navWrapRef = useRef(null);
|
|
361
460
|
|
|
@@ -704,14 +803,60 @@ function Navigation({
|
|
|
704
803
|
};
|
|
705
804
|
}, [debugIcons]);
|
|
706
805
|
|
|
806
|
+
/**
|
|
807
|
+
* `navConfig.actionsPlacement === 'sidebar'` moves the account actions
|
|
808
|
+
* out of the 50px content header and pins them to the bottom of the
|
|
809
|
+
* rail, which is otherwise empty space, so the page gets the whole
|
|
810
|
+
* height of the glass. Every project that publishes nothing — or
|
|
811
|
+
* 'header' — renders exactly what it rendered before.
|
|
812
|
+
*/
|
|
813
|
+
const actionsInSidebar =
|
|
814
|
+
(layout === 'simple' || layout === 'cms') &&
|
|
815
|
+
navConfig.actionsPlacement === 'sidebar';
|
|
816
|
+
const searchEnabled = navConfig.search?.enable === true;
|
|
817
|
+
/**
|
|
818
|
+
* With the actions gone the header holds a search field the project may
|
|
819
|
+
* not have enabled and a hamburger that only exists below 1024px. In
|
|
820
|
+
* that case it stays in the markup — the drawer is anchored under it and
|
|
821
|
+
* offsets itself by its 50px — but desktop drops it, and the main
|
|
822
|
+
* container starts at the top of the viewport.
|
|
823
|
+
*/
|
|
824
|
+
const headerIsMobileOnly = actionsInSidebar && !searchEnabled;
|
|
825
|
+
|
|
826
|
+
// Built only when a project asks for it, so the default path does no
|
|
827
|
+
// work it then throws away.
|
|
828
|
+
const railActions = !actionsInSidebar ? null : (
|
|
829
|
+
<div
|
|
830
|
+
className={styles['rail-actions']}
|
|
831
|
+
/* Read by the stylesheets of the components mounted in here —
|
|
832
|
+
the notification panel and the account switcher list both
|
|
833
|
+
anchor to a header chip by default, which from a rail at the
|
|
834
|
+
left edge would open off-screen. */
|
|
835
|
+
data-nav-actions="sidebar"
|
|
836
|
+
ref={actionItemsRef}
|
|
837
|
+
>
|
|
838
|
+
<ul>
|
|
839
|
+
{navData.settings.map((setting, settingKey) =>
|
|
840
|
+
setting.hidden !== true && setting.permission === true ? (
|
|
841
|
+
<React.Fragment key={`rail-setting-${settingKey}`}>
|
|
842
|
+
{renderRailSetting(setting)}
|
|
843
|
+
</React.Fragment>
|
|
844
|
+
) : null
|
|
845
|
+
)}
|
|
846
|
+
</ul>
|
|
847
|
+
</div>
|
|
848
|
+
);
|
|
849
|
+
|
|
707
850
|
return layout === 'simple' || layout === 'cms' ? (
|
|
708
851
|
<div className={styles.cwrap}>
|
|
709
852
|
<aside
|
|
710
|
-
className={
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
853
|
+
className={[
|
|
854
|
+
styles['aside--cms'],
|
|
855
|
+
isOpen ? styles['aside--cms--open'] : '',
|
|
856
|
+
actionsInSidebar ? styles['aside--cms--rail'] : '',
|
|
857
|
+
]
|
|
858
|
+
.filter(Boolean)
|
|
859
|
+
.join(' ')}
|
|
715
860
|
>
|
|
716
861
|
<div className={styles.logo}>
|
|
717
862
|
<Link to="/">
|
|
@@ -733,6 +878,7 @@ function Navigation({
|
|
|
733
878
|
</ul>
|
|
734
879
|
</nav>
|
|
735
880
|
</div>
|
|
881
|
+
{railActions}
|
|
736
882
|
</aside>
|
|
737
883
|
{isOpen ? (
|
|
738
884
|
<button
|
|
@@ -742,8 +888,20 @@ function Navigation({
|
|
|
742
888
|
onClick={() => setOpen(false)}
|
|
743
889
|
/>
|
|
744
890
|
) : null}
|
|
745
|
-
<main
|
|
746
|
-
|
|
891
|
+
<main
|
|
892
|
+
className={
|
|
893
|
+
headerIsMobileOnly
|
|
894
|
+
? `${styles.content} ${styles['content--full']}`
|
|
895
|
+
: styles.content
|
|
896
|
+
}
|
|
897
|
+
>
|
|
898
|
+
<div
|
|
899
|
+
className={
|
|
900
|
+
headerIsMobileOnly
|
|
901
|
+
? `${styles['content-header']} ${styles['content-header--mobile']}`
|
|
902
|
+
: styles['content-header']
|
|
903
|
+
}
|
|
904
|
+
>
|
|
747
905
|
{navConfig.search?.enable === true && (
|
|
748
906
|
<SearchComponent
|
|
749
907
|
navigate={navigate}
|
|
@@ -774,25 +932,27 @@ function Navigation({
|
|
|
774
932
|
<span></span>
|
|
775
933
|
</button>
|
|
776
934
|
</div>
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
setting
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
935
|
+
{actionsInSidebar ? null : (
|
|
936
|
+
<div
|
|
937
|
+
className={`${styles['hactions-alternate']} ${
|
|
938
|
+
debugIcons ? styles['debug-icons'] : ''
|
|
939
|
+
}`}
|
|
940
|
+
ref={actionItemsRef}
|
|
941
|
+
>
|
|
942
|
+
<ul>
|
|
943
|
+
{navData.settings.map((setting, settingKey) =>
|
|
944
|
+
setting.hidden !== true &&
|
|
945
|
+
setting.permission === true ? (
|
|
946
|
+
<React.Fragment
|
|
947
|
+
key={`setting-${settingKey}`}
|
|
948
|
+
>
|
|
949
|
+
{renderSetting(setting)}
|
|
950
|
+
</React.Fragment>
|
|
951
|
+
) : null
|
|
952
|
+
)}
|
|
953
|
+
</ul>
|
|
954
|
+
</div>
|
|
955
|
+
)}
|
|
796
956
|
</div>
|
|
797
957
|
{children}
|
|
798
958
|
</main>
|