@visns-studio/visns-components 6.3.7 → 6.3.9
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
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.3.
|
|
94
|
+
"version": "6.3.9",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -75,6 +75,13 @@ import {
|
|
|
75
75
|
AlertCircle,
|
|
76
76
|
CheckCheck,
|
|
77
77
|
Plus,
|
|
78
|
+
// treeGroupBy parent rows: ChevronDown is the collapse control in BOTH
|
|
79
|
+
// states — it is rotated rather than swapped for ChevronRight, so the
|
|
80
|
+
// glyph turns between them instead of popping, and the direction it
|
|
81
|
+
// points is the state. ListPlus is the "add another to this group"
|
|
82
|
+
// affordance, which a bare Plus does not say on its own.
|
|
83
|
+
ChevronDown,
|
|
84
|
+
ListPlus,
|
|
78
85
|
} from 'lucide-react';
|
|
79
86
|
import styles from './styles/DataGrid.module.scss';
|
|
80
87
|
|
|
@@ -659,11 +666,11 @@ const DataGrid = forwardRef(
|
|
|
659
666
|
}
|
|
660
667
|
|
|
661
668
|
// Opt-in grouped display (ajaxSetting.treeGroupBy {key,
|
|
662
|
-
// column, parentFields,
|
|
663
|
-
// defaultCollapsed, icons}): consecutive rows
|
|
664
|
-
// non-empty `key` value get a synthetic summary
|
|
665
|
-
// row inserted above them. `parentFields` are lifted
|
|
666
|
-
// the first row onto the parent and blanked on the
|
|
669
|
+
// column, parentFields, parentFieldAliases, countLabel,
|
|
670
|
+
// collapsible, defaultCollapsed, icons}): consecutive rows
|
|
671
|
+
// sharing a non-empty `key` value get a synthetic summary
|
|
672
|
+
// parent row inserted above them. `parentFields` are lifted
|
|
673
|
+
// from the first row onto the parent and blanked on the
|
|
667
674
|
// children, so shared details display once; children get
|
|
668
675
|
// an "↳" prefix in the display column. Single rows stay
|
|
669
676
|
// plain — unlike the grid's own groupBy, which forces a
|
|
@@ -678,6 +685,7 @@ const DataGrid = forwardRef(
|
|
|
678
685
|
key: treeKey,
|
|
679
686
|
column: treeDisplayColumn,
|
|
680
687
|
parentFields = [],
|
|
688
|
+
parentFieldAliases = {},
|
|
681
689
|
countLabel,
|
|
682
690
|
collapsible,
|
|
683
691
|
defaultCollapsed,
|
|
@@ -722,6 +730,35 @@ const DataGrid = forwardRef(
|
|
|
722
730
|
});
|
|
723
731
|
});
|
|
724
732
|
|
|
733
|
+
// `parentFieldAliases` ({columnField:
|
|
734
|
+
// sourceField}): the parent shows a DIFFERENT
|
|
735
|
+
// field in a column than its children do, so
|
|
736
|
+
// one column can carry the per-member value on
|
|
737
|
+
// members and the group's summary on the
|
|
738
|
+
// parent instead of spending a second column
|
|
739
|
+
// on the summary alone.
|
|
740
|
+
//
|
|
741
|
+
// Unlike `parentFields` this deliberately does
|
|
742
|
+
// NOT blank the column on the children — the
|
|
743
|
+
// members' own values are the reason the
|
|
744
|
+
// column exists. (Listing the same field in
|
|
745
|
+
// both would blank it on the children and
|
|
746
|
+
// defeat that, so pick one.)
|
|
747
|
+
//
|
|
748
|
+
// Distinct from `countLabel` too, which
|
|
749
|
+
// templates a summary from the child COUNT and
|
|
750
|
+
// so can only produce one fixed wording. An
|
|
751
|
+
// alias reads a field the backend computed,
|
|
752
|
+
// which is what it takes for the summary to
|
|
753
|
+
// vary with the data ("2 Headers" for one kind
|
|
754
|
+
// of group, "2 Stations" for another).
|
|
755
|
+
Object.entries(parentFieldAliases).forEach(
|
|
756
|
+
([columnField, sourceField]) => {
|
|
757
|
+
parent[columnField] =
|
|
758
|
+
children[0]?.[sourceField];
|
|
759
|
+
}
|
|
760
|
+
);
|
|
761
|
+
|
|
725
762
|
// Mark children for styling (getRowStyle) and
|
|
726
763
|
// prefix their display value so nesting reads
|
|
727
764
|
children.forEach((child) => {
|
|
@@ -1796,6 +1833,10 @@ const DataGrid = forwardRef(
|
|
|
1796
1833
|
return CheckCheck;
|
|
1797
1834
|
case 'plus':
|
|
1798
1835
|
return Plus;
|
|
1836
|
+
// A separate id rather than a re-point of `plus`: every other
|
|
1837
|
+
// project's `plus` icon must keep the glyph it has always had.
|
|
1838
|
+
case 'listPlus':
|
|
1839
|
+
return ListPlus;
|
|
1799
1840
|
case 'edit':
|
|
1800
1841
|
return Edit;
|
|
1801
1842
|
case 'envelope':
|
|
@@ -4110,9 +4151,125 @@ const DataGrid = forwardRef(
|
|
|
4110
4151
|
};
|
|
4111
4152
|
};
|
|
4112
4153
|
|
|
4154
|
+
/**
|
|
4155
|
+
* The group's collapse control.
|
|
4156
|
+
*
|
|
4157
|
+
* One glyph in both states: ChevronDown, turned a quarter
|
|
4158
|
+
* to point right when the group is shut. That is the
|
|
4159
|
+
* idiom every tree in every file manager uses, and it
|
|
4160
|
+
* animates — a "+"/"−" pair swaps two unrelated marks and
|
|
4161
|
+
* says nothing about direction. `relocated` picks the
|
|
4162
|
+
* class set, since the controls-column variant is sized
|
|
4163
|
+
* for a fingertip and must not inherit the inline one's
|
|
4164
|
+
* legacy dimensions.
|
|
4165
|
+
*
|
|
4166
|
+
* aria-expanded carries the state for assistive tech, so
|
|
4167
|
+
* the meaning does not depend on seeing the rotation.
|
|
4168
|
+
*/
|
|
4169
|
+
const renderTreeCollapseControl = (
|
|
4170
|
+
groupValue,
|
|
4171
|
+
collapsed,
|
|
4172
|
+
relocated
|
|
4173
|
+
) => (
|
|
4174
|
+
<button
|
|
4175
|
+
type="button"
|
|
4176
|
+
className={
|
|
4177
|
+
relocated
|
|
4178
|
+
? `tree-control-btn ${styles.treeControlBtn} ${styles.treeControlChevron}`
|
|
4179
|
+
: `group-expand-btn ${styles.groupExpandBtn}`
|
|
4180
|
+
}
|
|
4181
|
+
aria-expanded={!collapsed}
|
|
4182
|
+
aria-label={
|
|
4183
|
+
collapsed ? 'Expand group' : 'Collapse group'
|
|
4184
|
+
}
|
|
4185
|
+
onClick={(e) => {
|
|
4186
|
+
e.stopPropagation();
|
|
4187
|
+
toggleTreeGroup(groupValue);
|
|
4188
|
+
}}
|
|
4189
|
+
data-tooltip-id="system-tooltip"
|
|
4190
|
+
data-tooltip-content={
|
|
4191
|
+
collapsed ? 'Expand group' : 'Collapse group'
|
|
4192
|
+
}
|
|
4193
|
+
>
|
|
4194
|
+
{relocated ? (
|
|
4195
|
+
<ChevronDown
|
|
4196
|
+
className={
|
|
4197
|
+
collapsed
|
|
4198
|
+
? styles.treeChevronCollapsed
|
|
4199
|
+
: styles.treeChevronExpanded
|
|
4200
|
+
}
|
|
4201
|
+
strokeWidth={2.25}
|
|
4202
|
+
size={18}
|
|
4203
|
+
aria-hidden="true"
|
|
4204
|
+
/>
|
|
4205
|
+
) : collapsed ? (
|
|
4206
|
+
'+'
|
|
4207
|
+
) : (
|
|
4208
|
+
'−'
|
|
4209
|
+
)}
|
|
4210
|
+
</button>
|
|
4211
|
+
);
|
|
4212
|
+
|
|
4213
|
+
/** The group's treeGroupBy.icons, in either placement. */
|
|
4214
|
+
const renderTreeGroupIcons = (groupValue, relocated) =>
|
|
4215
|
+
(ajaxSetting?.treeGroupBy?.icons || []).map(
|
|
4216
|
+
(iconConfig, index) => {
|
|
4217
|
+
if (
|
|
4218
|
+
!shouldShowGroupAction(
|
|
4219
|
+
iconConfig,
|
|
4220
|
+
groupValue
|
|
4221
|
+
)
|
|
4222
|
+
) {
|
|
4223
|
+
return null;
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
const IconComponent = getGroupIconComponent(
|
|
4227
|
+
iconConfig.id
|
|
4228
|
+
);
|
|
4229
|
+
const colour = iconConfig.colour || 'success';
|
|
4230
|
+
|
|
4231
|
+
return (
|
|
4232
|
+
<button
|
|
4233
|
+
type="button"
|
|
4234
|
+
key={`tree-action-${index}`}
|
|
4235
|
+
className={
|
|
4236
|
+
relocated
|
|
4237
|
+
? `tree-control-btn tree-control-${iconConfig.id}-btn ${styles.treeControlBtn} ${styles[colour] || ''}`
|
|
4238
|
+
: `group-action-btn ${styles.groupActionBtn} ${styles[colour] || ''}`
|
|
4239
|
+
}
|
|
4240
|
+
aria-label={iconConfig.label}
|
|
4241
|
+
onClick={(e) => {
|
|
4242
|
+
e.stopPropagation();
|
|
4243
|
+
handleGroupAction(
|
|
4244
|
+
groupValue,
|
|
4245
|
+
iconConfig
|
|
4246
|
+
);
|
|
4247
|
+
}}
|
|
4248
|
+
data-tooltip-id="system-tooltip"
|
|
4249
|
+
data-tooltip-content={iconConfig.label}
|
|
4250
|
+
>
|
|
4251
|
+
<IconComponent
|
|
4252
|
+
strokeWidth={2}
|
|
4253
|
+
size={relocated ? 18 : 14}
|
|
4254
|
+
aria-hidden="true"
|
|
4255
|
+
/>
|
|
4256
|
+
</button>
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
);
|
|
4260
|
+
|
|
4113
4261
|
// The tree parent's display column doubles as the group's
|
|
4114
4262
|
// header line: expand/collapse plus any treeGroupBy.icons
|
|
4115
4263
|
// whose show/showAll conditions the group's rows satisfy.
|
|
4264
|
+
//
|
|
4265
|
+
// `controlsColumn` (opt-in) moves those controls into
|
|
4266
|
+
// ANOTHER column's cell on parent rows, leaving the label
|
|
4267
|
+
// cell to the label alone. Parent rows blank most
|
|
4268
|
+
// interactive columns (TREE_PARENT_BLOCKED_TYPES), so a
|
|
4269
|
+
// grid usually has an empty cell going spare while the
|
|
4270
|
+
// label is squeezed between two buttons — this spends that
|
|
4271
|
+
// space instead. Unset (the default) keeps the controls
|
|
4272
|
+
// inline exactly as they have always rendered.
|
|
4116
4273
|
const decorateTreeParentActions = (
|
|
4117
4274
|
gridColumn,
|
|
4118
4275
|
columnDef
|
|
@@ -4125,12 +4282,29 @@ const DataGrid = forwardRef(
|
|
|
4125
4282
|
if (
|
|
4126
4283
|
!tree?.key ||
|
|
4127
4284
|
!tree.column ||
|
|
4128
|
-
tree.column !== columnId ||
|
|
4129
4285
|
!(tree.collapsible || tree.icons?.length)
|
|
4130
4286
|
) {
|
|
4131
4287
|
return gridColumn;
|
|
4132
4288
|
}
|
|
4133
4289
|
|
|
4290
|
+
// Pointing controlsColumn at the label column is the
|
|
4291
|
+
// inline layout, so treat it as unset rather than
|
|
4292
|
+
// rendering the controls into the cell twice.
|
|
4293
|
+
const controlsColumn =
|
|
4294
|
+
tree.controlsColumn &&
|
|
4295
|
+
tree.controlsColumn !== tree.column
|
|
4296
|
+
? tree.controlsColumn
|
|
4297
|
+
: null;
|
|
4298
|
+
|
|
4299
|
+
const isLabelColumn = tree.column === columnId;
|
|
4300
|
+
const isControlsColumn =
|
|
4301
|
+
controlsColumn !== null &&
|
|
4302
|
+
controlsColumn === columnId;
|
|
4303
|
+
|
|
4304
|
+
if (!isLabelColumn && !isControlsColumn) {
|
|
4305
|
+
return gridColumn;
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4134
4308
|
const originalRender = gridColumn.render;
|
|
4135
4309
|
|
|
4136
4310
|
return {
|
|
@@ -4147,28 +4321,44 @@ const DataGrid = forwardRef(
|
|
|
4147
4321
|
const groupValue = data[tree.key];
|
|
4148
4322
|
const collapsed = data.__treeCollapsed === true;
|
|
4149
4323
|
|
|
4324
|
+
// Relocated placement: this cell carries the
|
|
4325
|
+
// controls and nothing else.
|
|
4326
|
+
if (isControlsColumn) {
|
|
4327
|
+
return (
|
|
4328
|
+
<div
|
|
4329
|
+
className={
|
|
4330
|
+
styles.treeParentControls
|
|
4331
|
+
}
|
|
4332
|
+
onClick={(e) =>
|
|
4333
|
+
e.stopPropagation()
|
|
4334
|
+
}
|
|
4335
|
+
>
|
|
4336
|
+
{tree.collapsible &&
|
|
4337
|
+
renderTreeCollapseControl(
|
|
4338
|
+
groupValue,
|
|
4339
|
+
collapsed,
|
|
4340
|
+
true
|
|
4341
|
+
)}
|
|
4342
|
+
{renderTreeGroupIcons(
|
|
4343
|
+
groupValue,
|
|
4344
|
+
true
|
|
4345
|
+
)}
|
|
4346
|
+
</div>
|
|
4347
|
+
);
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4150
4350
|
return (
|
|
4151
4351
|
<div
|
|
4152
4352
|
className={styles.treeParentHeader}
|
|
4153
4353
|
onClick={(e) => e.stopPropagation()}
|
|
4154
4354
|
>
|
|
4155
|
-
{
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
data-tooltip-id="system-tooltip"
|
|
4163
|
-
data-tooltip-content={
|
|
4164
|
-
collapsed
|
|
4165
|
-
? 'Expand group'
|
|
4166
|
-
: 'Collapse group'
|
|
4167
|
-
}
|
|
4168
|
-
>
|
|
4169
|
-
{collapsed ? '+' : '−'}
|
|
4170
|
-
</button>
|
|
4171
|
-
)}
|
|
4355
|
+
{!controlsColumn &&
|
|
4356
|
+
tree.collapsible &&
|
|
4357
|
+
renderTreeCollapseControl(
|
|
4358
|
+
groupValue,
|
|
4359
|
+
collapsed,
|
|
4360
|
+
false
|
|
4361
|
+
)}
|
|
4172
4362
|
<strong
|
|
4173
4363
|
className={styles.treeParentLabel}
|
|
4174
4364
|
>
|
|
@@ -4177,53 +4367,11 @@ const DataGrid = forwardRef(
|
|
|
4177
4367
|
? ` (${data.__treeChildCount})`
|
|
4178
4368
|
: ''}
|
|
4179
4369
|
</strong>
|
|
4180
|
-
{
|
|
4181
|
-
(
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
groupValue
|
|
4186
|
-
)
|
|
4187
|
-
) {
|
|
4188
|
-
return null;
|
|
4189
|
-
}
|
|
4190
|
-
|
|
4191
|
-
const IconComponent =
|
|
4192
|
-
getGroupIconComponent(
|
|
4193
|
-
iconConfig.id
|
|
4194
|
-
);
|
|
4195
|
-
|
|
4196
|
-
return (
|
|
4197
|
-
<button
|
|
4198
|
-
key={`tree-action-${index}`}
|
|
4199
|
-
className={`group-action-btn ${
|
|
4200
|
-
styles.groupActionBtn
|
|
4201
|
-
} ${
|
|
4202
|
-
styles[
|
|
4203
|
-
iconConfig.colour ||
|
|
4204
|
-
'success'
|
|
4205
|
-
] || ''
|
|
4206
|
-
}`}
|
|
4207
|
-
onClick={(e) => {
|
|
4208
|
-
e.stopPropagation();
|
|
4209
|
-
handleGroupAction(
|
|
4210
|
-
groupValue,
|
|
4211
|
-
iconConfig
|
|
4212
|
-
);
|
|
4213
|
-
}}
|
|
4214
|
-
data-tooltip-id="system-tooltip"
|
|
4215
|
-
data-tooltip-content={
|
|
4216
|
-
iconConfig.label
|
|
4217
|
-
}
|
|
4218
|
-
>
|
|
4219
|
-
<IconComponent
|
|
4220
|
-
strokeWidth={2}
|
|
4221
|
-
size={14}
|
|
4222
|
-
/>
|
|
4223
|
-
</button>
|
|
4224
|
-
);
|
|
4225
|
-
}
|
|
4226
|
-
)}
|
|
4370
|
+
{!controlsColumn &&
|
|
4371
|
+
renderTreeGroupIcons(
|
|
4372
|
+
groupValue,
|
|
4373
|
+
false
|
|
4374
|
+
)}
|
|
4227
4375
|
</div>
|
|
4228
4376
|
);
|
|
4229
4377
|
},
|
|
@@ -1402,3 +1402,180 @@
|
|
|
1402
1402
|
opacity: var(--grid-row-complete-opacity, 0.7);
|
|
1403
1403
|
}
|
|
1404
1404
|
}
|
|
1405
|
+
|
|
1406
|
+
/* =========================================================================
|
|
1407
|
+
treeGroupBy relocated group controls (`treeGroupBy.controlsColumn`)
|
|
1408
|
+
|
|
1409
|
+
When the controls move out of the label cell they stop being decoration
|
|
1410
|
+
squeezed beside a tag and become the row's primary affordance, so they are
|
|
1411
|
+
sized as targets rather than as glyphs. They also get class names of their
|
|
1412
|
+
own rather than reusing `.groupExpandBtn` / `.groupActionBtn`: consuming
|
|
1413
|
+
projects have `!important` rules pinned to those (hartway clamps them to
|
|
1414
|
+
38px in tablet mode), and inheriting a size tuned for the cramped inline
|
|
1415
|
+
layout is exactly what this placement exists to escape.
|
|
1416
|
+
|
|
1417
|
+
Meaning never rests on colour: each control carries a glyph, the collapse
|
|
1418
|
+
state is carried by the chevron's rotation AND by aria-expanded, and the
|
|
1419
|
+
colour classes below are the same decorative palette the inline buttons
|
|
1420
|
+
use. A greyscale screen loses nothing.
|
|
1421
|
+
|
|
1422
|
+
Density hooks, per the `--nav-*` / `--grid-legend-*` convention above:
|
|
1423
|
+
|
|
1424
|
+
--grid-tree-control-size control box, pointer input. default: 32px
|
|
1425
|
+
--grid-tree-control-size-tablet control box under body.tablet-mode.
|
|
1426
|
+
default: 44px — the tap-target floor for
|
|
1427
|
+
a gloved hand on the plant floor, and the
|
|
1428
|
+
reason this placement is worth having.
|
|
1429
|
+
--grid-tree-control-gap space between controls. default: 6px
|
|
1430
|
+
========================================================================= */
|
|
1431
|
+
|
|
1432
|
+
.treeParentControls {
|
|
1433
|
+
display: flex;
|
|
1434
|
+
align-items: center;
|
|
1435
|
+
justify-content: flex-start;
|
|
1436
|
+
gap: var(--grid-tree-control-gap, 6px);
|
|
1437
|
+
width: 100%;
|
|
1438
|
+
height: 100%;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
.treeControlBtn {
|
|
1442
|
+
flex-shrink: 0;
|
|
1443
|
+
display: inline-flex;
|
|
1444
|
+
align-items: center;
|
|
1445
|
+
justify-content: center;
|
|
1446
|
+
box-sizing: border-box;
|
|
1447
|
+
width: var(--grid-tree-control-size, 32px);
|
|
1448
|
+
height: var(--grid-tree-control-size, 32px);
|
|
1449
|
+
min-width: var(--grid-tree-control-size, 32px);
|
|
1450
|
+
min-height: var(--grid-tree-control-size, 32px);
|
|
1451
|
+
padding: 0;
|
|
1452
|
+
border: none;
|
|
1453
|
+
border-radius: var(--radius-sm, 6px);
|
|
1454
|
+
background: var(--primary-color, #3b82f6);
|
|
1455
|
+
color: white;
|
|
1456
|
+
cursor: pointer;
|
|
1457
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
|
|
1458
|
+
transition: background 0.2s ease, box-shadow 0.2s ease,
|
|
1459
|
+
transform 0.1s ease;
|
|
1460
|
+
|
|
1461
|
+
&:hover {
|
|
1462
|
+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.18);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
&:active {
|
|
1466
|
+
transform: scale(0.94);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// A keyboard user has to be able to see which control is theirs; the grid
|
|
1470
|
+
// suppresses its own outline inside cells, so state it here.
|
|
1471
|
+
&:focus-visible {
|
|
1472
|
+
outline: 2px solid var(--primary-color, #3b82f6);
|
|
1473
|
+
outline-offset: 2px;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Colour variants, matching `.groupActionBtn`'s set so an icon config
|
|
1477
|
+
// reads the same in either placement.
|
|
1478
|
+
&.success {
|
|
1479
|
+
background: var(--success-color, #10b981);
|
|
1480
|
+
&:hover {
|
|
1481
|
+
background: var(--success-hover-color, #059669);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
&.warning {
|
|
1486
|
+
background: var(--warning-color, #f59e0b);
|
|
1487
|
+
&:hover {
|
|
1488
|
+
background: var(--warning-hover-color, #d97706);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
&.info {
|
|
1493
|
+
background: var(--info-color, #3b82f6);
|
|
1494
|
+
&:hover {
|
|
1495
|
+
background: var(--info-hover-color, #2563eb);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
&.purple {
|
|
1500
|
+
background: var(--purple-color, #8b5cf6);
|
|
1501
|
+
&:hover {
|
|
1502
|
+
background: var(--purple-hover-color, #7c3aed);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
&.pink {
|
|
1507
|
+
background: var(--pink-color, #ec4899);
|
|
1508
|
+
&:hover {
|
|
1509
|
+
background: var(--pink-hover-color, #db2777);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/* The collapse control is deliberately quieter than the action buttons: it
|
|
1515
|
+
changes what you can see, not what the plant has done, so it reads as
|
|
1516
|
+
chrome rather than as a fourth coloured button competing with them. */
|
|
1517
|
+
.treeControlChevron {
|
|
1518
|
+
background: rgba(var(--paragraph-color-rgb, 34, 30, 51), 0.08);
|
|
1519
|
+
color: var(--paragraph-color, #374151);
|
|
1520
|
+
box-shadow: none;
|
|
1521
|
+
|
|
1522
|
+
&:hover {
|
|
1523
|
+
background: rgba(var(--paragraph-color-rgb, 34, 30, 51), 0.16);
|
|
1524
|
+
box-shadow: none;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
.treeChevronExpanded,
|
|
1529
|
+
.treeChevronCollapsed {
|
|
1530
|
+
transition: transform 0.18s ease;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.treeChevronExpanded {
|
|
1534
|
+
transform: rotate(0deg);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
/* Shut: the chevron points at the row it would open, the same way every
|
|
1538
|
+
file tree in the world points at a closed folder. */
|
|
1539
|
+
.treeChevronCollapsed {
|
|
1540
|
+
transform: rotate(-90deg);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1544
|
+
.treeChevronExpanded,
|
|
1545
|
+
.treeChevronCollapsed {
|
|
1546
|
+
transition: none;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/* Tablet mode is a bench at arm's length worked with gloves on, so the
|
|
1551
|
+
controls step up to the 44px tap-target floor. Scoped to body.tablet-mode,
|
|
1552
|
+
the same signal the package already styles against in global-datagrid.css.
|
|
1553
|
+
The grid's tablet rows are 52px, so 44px still clears the row edges. */
|
|
1554
|
+
:global(body.tablet-mode) {
|
|
1555
|
+
.treeControlBtn {
|
|
1556
|
+
width: var(--grid-tree-control-size-tablet, 44px);
|
|
1557
|
+
height: var(--grid-tree-control-size-tablet, 44px);
|
|
1558
|
+
min-width: var(--grid-tree-control-size-tablet, 44px);
|
|
1559
|
+
min-height: var(--grid-tree-control-size-tablet, 44px);
|
|
1560
|
+
border-radius: var(--radius-md, 8px);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
.treeParentControls {
|
|
1564
|
+
gap: var(--grid-tree-control-gap, 8px);
|
|
1565
|
+
|
|
1566
|
+
/* A tablet row is a fixed 52px and the grid pads its cells 6px top
|
|
1567
|
+
and bottom, leaving a 40px content box — 4px short of the floor
|
|
1568
|
+
this placement exists to clear. This strip IS the whole cell's
|
|
1569
|
+
content and its buttons carry their own spacing, so it reclaims
|
|
1570
|
+
that padding rather than letting the control shrink to fit.
|
|
1571
|
+
Negative margin rather than overflow: the 44px control then sits
|
|
1572
|
+
inside the row's own 52px border box, so a cell that hides its
|
|
1573
|
+
overflow has nothing to clip.
|
|
1574
|
+
|
|
1575
|
+
--grid-tree-control-cell-inset cell padding to reclaim.
|
|
1576
|
+
default: 6px */
|
|
1577
|
+
margin-block: calc(
|
|
1578
|
+
-1 * var(--grid-tree-control-cell-inset, 6px)
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
}
|