@estiva-app/ui 0.57.4 → 0.59.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/dist/CommandPalette.d.ts.map +1 -1
- package/dist/ComposerTriggers.d.ts +13 -1
- package/dist/ComposerTriggers.d.ts.map +1 -1
- package/dist/ContainerHeader.d.ts.map +1 -1
- package/dist/DialogShell.d.ts.map +1 -1
- package/dist/Kbd.d.ts.map +1 -1
- package/dist/MembersDialog.d.ts +57 -0
- package/dist/MembersDialog.d.ts.map +1 -0
- package/dist/MembersPill.d.ts +22 -0
- package/dist/MembersPill.d.ts.map +1 -0
- package/dist/NavItem.d.ts.map +1 -1
- package/dist/SectionHeader.d.ts +7 -1
- package/dist/SectionHeader.d.ts.map +1 -1
- package/dist/ShowMore.d.ts +27 -0
- package/dist/ShowMore.d.ts.map +1 -0
- package/dist/Tabs.d.ts +2 -0
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/editor.js +4 -4
- package/dist/editor.js.map +2 -2
- package/dist/eslint/index.js +1 -0
- package/dist/eslint/index.js.map +2 -2
- package/dist/eslint/no-restyled-part.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1150 -994
- package/dist/index.js.map +4 -4
- package/dist/looks.d.ts +4 -0
- package/dist/looks.d.ts.map +1 -1
- package/package.json +1 -1
- package/registry.json +212 -8
- package/src/CommandPalette.tsx +2 -1
- package/src/ComposerTriggers.mdx +7 -0
- package/src/ComposerTriggers.stories.tsx +43 -4
- package/src/ComposerTriggers.test.tsx +33 -0
- package/src/ComposerTriggers.tsx +17 -5
- package/src/ContainerHeader.mdx +1 -1
- package/src/ContainerHeader.tsx +3 -2
- package/src/DialogShell.tsx +2 -0
- package/src/Kbd.tsx +4 -2
- package/src/ListColumn.mdx +1 -1
- package/src/MembersDialog.mdx +69 -0
- package/src/MembersDialog.stories.tsx +68 -0
- package/src/MembersDialog.test.tsx +69 -0
- package/src/MembersDialog.tsx +184 -0
- package/src/MembersPill.mdx +45 -0
- package/src/MembersPill.stories.tsx +38 -0
- package/src/MembersPill.tsx +35 -0
- package/src/NavItem.tsx +3 -2
- package/src/SectionHeader.mdx +6 -4
- package/src/SectionHeader.stories.tsx +7 -2
- package/src/SectionHeader.test.tsx +6 -0
- package/src/SectionHeader.tsx +10 -2
- package/src/ShowMore.mdx +62 -0
- package/src/ShowMore.stories.tsx +39 -0
- package/src/ShowMore.test.tsx +44 -0
- package/src/ShowMore.tsx +63 -0
- package/src/Tabs.mdx +5 -0
- package/src/Tabs.stories.tsx +15 -0
- package/src/Tabs.test.tsx +8 -0
- package/src/Tabs.tsx +12 -0
- package/src/eslint/no-restyled-part.ts +1 -0
- package/src/index.ts +3 -0
- package/src/looks.ts +6 -0
- package/src/registry/registry.test.ts +4 -4
- package/stories/Choosing.mdx +4 -1
package/dist/looks.d.ts
CHANGED
|
@@ -29,8 +29,12 @@ export declare const FIELD_SIZE_CLASSES: {
|
|
|
29
29
|
export declare const FIELD_SHELL_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong rounded-lg transition-colors";
|
|
30
30
|
/** A panel that floats over the page: Menu's panel and Tooltip. */
|
|
31
31
|
export declare const FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
|
|
32
|
+
/** A count beside a label, in the muted colour: NavItem's number and SectionHeader's `count`. */
|
|
33
|
+
export declare const COUNT_TEXT_CLASSES = "font-mono text-caption tabular-nums text-text-muted";
|
|
32
34
|
/** A chip's words: Chip's label and Reaction's count. */
|
|
33
35
|
export declare const CHIP_TEXT_CLASSES = "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums";
|
|
36
|
+
/** Small secondary words that turn mono under signal: Kbd's key, CommandPalette's footer and MembersPill's count. */
|
|
37
|
+
export declare const MONO_CAPTION_CLASSES = "text-caption text-text-secondary signal:font-mono signal:text-small";
|
|
34
38
|
/** Words typed straight into an input with no box of its own: SearchInput and ChipInput. */
|
|
35
39
|
export declare const BARE_INPUT_CLASSES = "bg-transparent text-text-primary placeholder:text-text-muted outline-none";
|
|
36
40
|
/** A sidebar row's shape and its words at rest, brightening under the pointer: NavItem, and SectionHeader's `row` (a folder among rows). */
|
package/dist/looks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../src/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,2MAA2M,CAAA;AAEzO,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,kDAAkD,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,iKAAiK,CAAA;AAEpM,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,4DAA4D,CAAA;AAEjG,kJAAkJ;AAClJ,eAAO,MAAM,kBAAkB;;;CAGrB,CAAA;AAEV,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,uJAAuJ,CAAA;AAEvL,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,qEAAqE,CAAA;AAE1G,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,0FAA0F,CAAA;AAExH,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8EAA8E,CAAA;AAE7G,4IAA4I;AAE5I,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AACjE,eAAO,MAAM,wBAAwB,gDAAgD,CAAA;AAErF,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,kEAAkE,CAAA;AAEpG,2IAA2I;AAC3I,eAAO,MAAM,oBAAoB,gGAAgG,CAAA;AAEjI;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kLAAkL,CAAA"}
|
|
1
|
+
{"version":3,"file":"looks.d.ts","sourceRoot":"","sources":["../src/looks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,2MAA2M,CAAA;AAEzO,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,kDAAkD,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,sBAAsB,iKAAiK,CAAA;AAEpM,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,4DAA4D,CAAA;AAEjG,kJAAkJ;AAClJ,eAAO,MAAM,kBAAkB;;;CAGrB,CAAA;AAEV,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,uJAAuJ,CAAA;AAEvL,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,qEAAqE,CAAA;AAE1G,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,wDAAwD,CAAA;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,0FAA0F,CAAA;AAExH,qHAAqH;AACrH,eAAO,MAAM,oBAAoB,wEAAwE,CAAA;AAEzG,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8EAA8E,CAAA;AAE7G,4IAA4I;AAE5I,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AACjE,eAAO,MAAM,wBAAwB,gDAAgD,CAAA;AAErF,2FAA2F;AAC3F,eAAO,MAAM,qBAAqB,kEAAkE,CAAA;AAEpG,2IAA2I;AAC3I,eAAO,MAAM,oBAAoB,gGAAgG,CAAA;AAEjI;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,kLAAkL,CAAA"}
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"kind": "package",
|
|
5
5
|
"repo": "estiva-ui",
|
|
6
6
|
"package": "@estiva-app/ui",
|
|
7
|
-
"packageVersion": "0.
|
|
8
|
-
"exports":
|
|
9
|
-
"typeExports":
|
|
7
|
+
"packageVersion": "0.59.0",
|
|
8
|
+
"exports": 133,
|
|
9
|
+
"typeExports": 140,
|
|
10
10
|
"files": null
|
|
11
11
|
},
|
|
12
12
|
"storybook": {
|
|
@@ -3152,10 +3152,9 @@
|
|
|
3152
3152
|
"docPage": "src/Kbd.mdx",
|
|
3153
3153
|
"app": null,
|
|
3154
3154
|
"looks": [
|
|
3155
|
-
"whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset font-sans text-caption text-text-secondary signal:border-b-2 signal:bg-bg-wash
|
|
3156
|
-
"whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset font-sans text-caption text-text-secondary signal:border-b-2 signal:bg-bg-wash
|
|
3157
|
-
"whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset font-sans
|
|
3158
|
-
"signal:border-b-2 signal:bg-bg-wash signal:font-mono signal:text-small",
|
|
3155
|
+
"whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset font-sans text-caption text-text-secondary signal:font-mono signal:text-small signal:border-b-2 signal:bg-bg-wash ship:border-b-2 ship:bg-bg-wash ship:font-mono ship:text-small leaf:font-mono leaf:text-small",
|
|
3156
|
+
"whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset font-sans text-caption text-text-secondary signal:font-mono signal:text-small signal:border-b-2 signal:bg-bg-wash ship:border-b-2 ship:bg-bg-wash ship:font-mono ship:text-small leaf:font-mono leaf:text-small border-border-tooltip-key bg-bg-tooltip-key text-text-tooltip-key signal:bg-bg-tooltip-key ship:bg-bg-tooltip-key",
|
|
3157
|
+
"whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset font-sans",
|
|
3159
3158
|
"ship:border-b-2 ship:bg-bg-wash ship:font-mono ship:text-small",
|
|
3160
3159
|
"border-border-tooltip-key bg-bg-tooltip-key text-text-tooltip-key signal:bg-bg-tooltip-key ship:bg-bg-tooltip-key"
|
|
3161
3160
|
]
|
|
@@ -3406,6 +3405,149 @@
|
|
|
3406
3405
|
"app": null,
|
|
3407
3406
|
"looks": []
|
|
3408
3407
|
},
|
|
3408
|
+
{
|
|
3409
|
+
"name": "MembersDialog",
|
|
3410
|
+
"repo": "estiva-ui",
|
|
3411
|
+
"kind": "component",
|
|
3412
|
+
"importPath": "@estiva-app/ui",
|
|
3413
|
+
"sourceFile": "src/MembersDialog.tsx",
|
|
3414
|
+
"purpose": "Who is in this, with joining, leaving and adding people — the list a MembersPill opens.",
|
|
3415
|
+
"purposeFrom": "page",
|
|
3416
|
+
"props": [
|
|
3417
|
+
{
|
|
3418
|
+
"name": "members",
|
|
3419
|
+
"takes": "MembersDialogMember[]",
|
|
3420
|
+
"required": true,
|
|
3421
|
+
"note": "The roster, in the order to draw it. The viewer's own row is the caller's to name (\"You\")."
|
|
3422
|
+
},
|
|
3423
|
+
{
|
|
3424
|
+
"name": "count",
|
|
3425
|
+
"takes": "number",
|
|
3426
|
+
"required": false,
|
|
3427
|
+
"note": "The number in the title's chip. Defaults to how many rows there are."
|
|
3428
|
+
},
|
|
3429
|
+
{
|
|
3430
|
+
"name": "self",
|
|
3431
|
+
"takes": "a handler",
|
|
3432
|
+
"required": false,
|
|
3433
|
+
"note": "The viewer's own row on top: \"Join\" when they are not in, \"Leave\" when they are."
|
|
3434
|
+
},
|
|
3435
|
+
{
|
|
3436
|
+
"name": "candidates",
|
|
3437
|
+
"takes": "MembersDialogCandidate[]",
|
|
3438
|
+
"required": false,
|
|
3439
|
+
"note": "Who \"Add members\" may offer. Members are left out by id."
|
|
3440
|
+
},
|
|
3441
|
+
{
|
|
3442
|
+
"name": "onAdd",
|
|
3443
|
+
"takes": "a handler",
|
|
3444
|
+
"required": false,
|
|
3445
|
+
"note": "Adds the people chosen. With it, the \"Add members\" row shows and opens the add layer."
|
|
3446
|
+
},
|
|
3447
|
+
{
|
|
3448
|
+
"name": "note",
|
|
3449
|
+
"takes": "anything",
|
|
3450
|
+
"required": false,
|
|
3451
|
+
"note": "One line under the roster — what membership does and does not cover."
|
|
3452
|
+
},
|
|
3453
|
+
{
|
|
3454
|
+
"name": "initialView",
|
|
3455
|
+
"takes": "list | add",
|
|
3456
|
+
"required": false,
|
|
3457
|
+
"note": "`add` opens straight on the add layer."
|
|
3458
|
+
},
|
|
3459
|
+
{
|
|
3460
|
+
"name": "onClose",
|
|
3461
|
+
"takes": "a handler",
|
|
3462
|
+
"required": true,
|
|
3463
|
+
"note": null
|
|
3464
|
+
}
|
|
3465
|
+
],
|
|
3466
|
+
"variants": [
|
|
3467
|
+
{
|
|
3468
|
+
"prop": "initialView",
|
|
3469
|
+
"values": [
|
|
3470
|
+
"list",
|
|
3471
|
+
"add"
|
|
3472
|
+
]
|
|
3473
|
+
}
|
|
3474
|
+
],
|
|
3475
|
+
"ownsBehaviours": [
|
|
3476
|
+
{
|
|
3477
|
+
"id": "portal",
|
|
3478
|
+
"behaviour": "Floats on top of the page"
|
|
3479
|
+
},
|
|
3480
|
+
{
|
|
3481
|
+
"id": "press-outside",
|
|
3482
|
+
"behaviour": "Closes on a press outside"
|
|
3483
|
+
},
|
|
3484
|
+
{
|
|
3485
|
+
"id": "page-keys",
|
|
3486
|
+
"behaviour": "Takes its keys by itself"
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
"id": "focus",
|
|
3490
|
+
"behaviour": "Holds focus inside while open, and gives it back"
|
|
3491
|
+
},
|
|
3492
|
+
{
|
|
3493
|
+
"id": "scroll-lock",
|
|
3494
|
+
"behaviour": "Stops the page behind it scrolling"
|
|
3495
|
+
}
|
|
3496
|
+
],
|
|
3497
|
+
"status": "stable",
|
|
3498
|
+
"migrationStage": null,
|
|
3499
|
+
"docsId": "primitives-membersdialog--docs",
|
|
3500
|
+
"storyId": "primitives-membersdialog--member",
|
|
3501
|
+
"docPage": "src/MembersDialog.mdx",
|
|
3502
|
+
"app": null,
|
|
3503
|
+
"looks": []
|
|
3504
|
+
},
|
|
3505
|
+
{
|
|
3506
|
+
"name": "MembersPill",
|
|
3507
|
+
"repo": "estiva-ui",
|
|
3508
|
+
"kind": "component",
|
|
3509
|
+
"importPath": "@estiva-app/ui",
|
|
3510
|
+
"sourceFile": "src/MembersPill.tsx",
|
|
3511
|
+
"purpose": "Who is in this: up to three faces and the total, in a small outlined pill that opens the full list.",
|
|
3512
|
+
"purposeFrom": "page",
|
|
3513
|
+
"props": [
|
|
3514
|
+
{
|
|
3515
|
+
"name": "members",
|
|
3516
|
+
"takes": "AvatarGroupMember[]",
|
|
3517
|
+
"required": true,
|
|
3518
|
+
"note": "Who is in it, in order. Up to three faces are drawn."
|
|
3519
|
+
},
|
|
3520
|
+
{
|
|
3521
|
+
"name": "count",
|
|
3522
|
+
"takes": "number",
|
|
3523
|
+
"required": false,
|
|
3524
|
+
"note": "The number beside the faces. Defaults to how many members there are."
|
|
3525
|
+
},
|
|
3526
|
+
{
|
|
3527
|
+
"name": "onClick",
|
|
3528
|
+
"takes": "a handler",
|
|
3529
|
+
"required": false,
|
|
3530
|
+
"note": "Opens the list — usually a `MembersDialog`."
|
|
3531
|
+
},
|
|
3532
|
+
{
|
|
3533
|
+
"name": "className",
|
|
3534
|
+
"takes": "text",
|
|
3535
|
+
"required": false,
|
|
3536
|
+
"note": null
|
|
3537
|
+
}
|
|
3538
|
+
],
|
|
3539
|
+
"variants": [],
|
|
3540
|
+
"ownsBehaviours": [],
|
|
3541
|
+
"status": "stable",
|
|
3542
|
+
"migrationStage": null,
|
|
3543
|
+
"docsId": "primitives-memberspill--docs",
|
|
3544
|
+
"storyId": "primitives-memberspill--two-members",
|
|
3545
|
+
"docPage": "src/MembersPill.mdx",
|
|
3546
|
+
"app": null,
|
|
3547
|
+
"looks": [
|
|
3548
|
+
"text-caption text-text-secondary signal:font-mono signal:text-small signal:tabular-nums"
|
|
3549
|
+
]
|
|
3550
|
+
},
|
|
3409
3551
|
{
|
|
3410
3552
|
"name": "Menu",
|
|
3411
3553
|
"repo": "estiva-ui",
|
|
@@ -4969,7 +5111,7 @@
|
|
|
4969
5111
|
"name": "icon",
|
|
4970
5112
|
"takes": "anything",
|
|
4971
5113
|
"required": true,
|
|
4972
|
-
"note": "At each row's start: a 16px icon."
|
|
5114
|
+
"note": "At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`)."
|
|
4973
5115
|
}
|
|
4974
5116
|
],
|
|
4975
5117
|
"variants": [],
|
|
@@ -5278,6 +5420,12 @@
|
|
|
5278
5420
|
"required": false,
|
|
5279
5421
|
"note": "Beside the title and always visible, before the actions — a count."
|
|
5280
5422
|
},
|
|
5423
|
+
{
|
|
5424
|
+
"name": "count",
|
|
5425
|
+
"takes": "number",
|
|
5426
|
+
"required": false,
|
|
5427
|
+
"note": "How many there are, 8px after the title — not at the row's end, where `trailing` goes (Katerina, 7 October: a heading over a list says its count beside its name, as a tab does)."
|
|
5428
|
+
},
|
|
5281
5429
|
{
|
|
5282
5430
|
"name": "actions",
|
|
5283
5431
|
"takes": "SectionAction[]",
|
|
@@ -5362,6 +5510,7 @@
|
|
|
5362
5510
|
"opacity-0 transition-[opacity,transform] duration-150 group-hover:opacity-100",
|
|
5363
5511
|
"opacity-0 transition-[opacity,transform] duration-150 group-hover:opacity-100 rotate-90",
|
|
5364
5512
|
"transition-transform duration-150 text-text-secondary rotate-90",
|
|
5513
|
+
"font-mono text-caption tabular-nums text-text-muted",
|
|
5365
5514
|
"rounded-lg transition-colors text-text-secondary hover:text-text-primary",
|
|
5366
5515
|
"rounded-lg transition-colors text-text-secondary hover:text-text-primary hover:bg-bg-hover",
|
|
5367
5516
|
"opacity-0 transition-opacity group-hover:opacity-100 [@media(hover:none)]:opacity-100"
|
|
@@ -5646,6 +5795,61 @@
|
|
|
5646
5795
|
"app": null,
|
|
5647
5796
|
"looks": []
|
|
5648
5797
|
},
|
|
5798
|
+
{
|
|
5799
|
+
"name": "ShowMore",
|
|
5800
|
+
"repo": "estiva-ui",
|
|
5801
|
+
"kind": "component",
|
|
5802
|
+
"importPath": "@estiva-app/ui",
|
|
5803
|
+
"sourceFile": "src/ShowMore.tsx",
|
|
5804
|
+
"purpose": "Long content cut at a height behind a fade, with \"Show more\" under it.",
|
|
5805
|
+
"purposeFrom": "page",
|
|
5806
|
+
"props": [
|
|
5807
|
+
{
|
|
5808
|
+
"name": "children",
|
|
5809
|
+
"takes": "anything",
|
|
5810
|
+
"required": true,
|
|
5811
|
+
"note": null
|
|
5812
|
+
},
|
|
5813
|
+
{
|
|
5814
|
+
"name": "maxHeight",
|
|
5815
|
+
"takes": "number",
|
|
5816
|
+
"required": false,
|
|
5817
|
+
"note": "The height to cut at, in px. Default 96: about seven lines of caption text."
|
|
5818
|
+
},
|
|
5819
|
+
{
|
|
5820
|
+
"name": "surface",
|
|
5821
|
+
"takes": "surface | inset",
|
|
5822
|
+
"required": false,
|
|
5823
|
+
"note": "What the content sits on, for the fade to end in: a panel's `surface` (default), or an inset card's `inset`."
|
|
5824
|
+
},
|
|
5825
|
+
{
|
|
5826
|
+
"name": "className",
|
|
5827
|
+
"takes": "text",
|
|
5828
|
+
"required": false,
|
|
5829
|
+
"note": null
|
|
5830
|
+
}
|
|
5831
|
+
],
|
|
5832
|
+
"variants": [
|
|
5833
|
+
{
|
|
5834
|
+
"prop": "surface",
|
|
5835
|
+
"values": [
|
|
5836
|
+
"surface",
|
|
5837
|
+
"inset"
|
|
5838
|
+
]
|
|
5839
|
+
}
|
|
5840
|
+
],
|
|
5841
|
+
"ownsBehaviours": [],
|
|
5842
|
+
"status": "stable",
|
|
5843
|
+
"migrationStage": null,
|
|
5844
|
+
"docsId": "layout-showmore--docs",
|
|
5845
|
+
"storyId": "layout-showmore--long",
|
|
5846
|
+
"docPage": "src/ShowMore.mdx",
|
|
5847
|
+
"app": null,
|
|
5848
|
+
"looks": [
|
|
5849
|
+
"pointer-events-none bg-gradient-to-t to-transparent from-bg-surface",
|
|
5850
|
+
"pointer-events-none bg-gradient-to-t to-transparent from-bg-inset"
|
|
5851
|
+
]
|
|
5852
|
+
},
|
|
5649
5853
|
{
|
|
5650
5854
|
"name": "Sidebar",
|
|
5651
5855
|
"repo": "estiva-ui",
|
package/src/CommandPalette.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { Autocomplete } from '@base-ui/react/autocomplete'
|
|
|
15
15
|
import type { BaseUIEvent } from '@base-ui/react/types'
|
|
16
16
|
import { IconLoader2, IconSearch } from '@tabler/icons-react'
|
|
17
17
|
import { cn } from './cn'
|
|
18
|
+
import { MONO_CAPTION_CLASSES } from './looks'
|
|
18
19
|
import { Button } from './Button'
|
|
19
20
|
import { Card } from './Card'
|
|
20
21
|
import { InputChip } from './ChipInput'
|
|
@@ -176,7 +177,7 @@ type Key = [key: string, word: string]
|
|
|
176
177
|
function Footer({ keys }: { keys: Key[] }) {
|
|
177
178
|
const { where } = usePalette('The footer')
|
|
178
179
|
return (
|
|
179
|
-
<div className=
|
|
180
|
+
<div className={cn('flex h-9 shrink-0 items-center gap-4 border-t border-border-subtle px-5', MONO_CAPTION_CLASSES, 'signal:text-text-muted leaf:font-mono leaf:text-small')}>
|
|
180
181
|
<span className="min-w-0 flex-1 truncate">{where}</span>
|
|
181
182
|
{keys.map(([key, word]) => (
|
|
182
183
|
<span key={key} className="flex shrink-0 items-center gap-1.5">
|
package/src/ComposerTriggers.mdx
CHANGED
|
@@ -92,6 +92,13 @@ const urgent = editor ? urgentPubkeys(editor) : undefined
|
|
|
92
92
|
the same query; nothing waits for it. A row's `description` is its second
|
|
93
93
|
line, which the chip does not wear. A row the person has arrowed to stays
|
|
94
94
|
highlighted when the list is drawn again.
|
|
95
|
+
- **Several kinds of thing in one `[` list:** a row's `icon` is drawn in
|
|
96
|
+
place of the list's, and the chip's `iconFor(uri)` picks the chip's from
|
|
97
|
+
what it points at, so the chip wears what its row wore. A row with no `icon`,
|
|
98
|
+
or a `uri` `iconFor` answers `undefined` for, keeps the one icon.
|
|
99
|
+
|
|
100
|
+
<Canvas of={ComposerTriggersStories.ReferencesOfSeveralKinds} />
|
|
101
|
+
|
|
95
102
|
- **An app's own node name:** `CaptionedReference.extend({ name: 'yourName' })`
|
|
96
103
|
and `ReferenceTrigger.configure({ nodeName: 'yourName' })`. The chip's HTML
|
|
97
104
|
attribute follows the name (`data-your-name`).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
-
import { IconSquareRounded } from '@tabler/icons-react'
|
|
2
|
+
import { IconCircle, IconSquareRounded, IconTriangle } from '@tabler/icons-react'
|
|
3
3
|
import { EditorContent, useEditor } from '@tiptap/react'
|
|
4
4
|
import StarterKit from '@tiptap/starter-kit'
|
|
5
5
|
import { richTextClassName } from './RichText'
|
|
@@ -59,6 +59,35 @@ const SECTIONS = (query: string): ReferenceSection[] => [
|
|
|
59
59
|
{ label: 'Group two', items: matching(DESCRIBED, query) },
|
|
60
60
|
]
|
|
61
61
|
|
|
62
|
+
/* Three kinds of thing in one list, each row with its own icon, and the chip it inserts wearing the same. */
|
|
63
|
+
const KIND_ICONS = { circle: IconCircle, triangle: IconTriangle, square: IconSquareRounded } as const
|
|
64
|
+
type Kind = keyof typeof KIND_ICONS
|
|
65
|
+
const kindOf = (uri: string | null): Kind | undefined => (Object.keys(KIND_ICONS) as Kind[]).find((kind) => uri?.includes(kind))
|
|
66
|
+
const KINDS = (query: string): ReferenceSection[] => [
|
|
67
|
+
{
|
|
68
|
+
label: 'Items',
|
|
69
|
+
items: matching(
|
|
70
|
+
(['circle', 'triangle', 'square'] as const).map((kind, i) => {
|
|
71
|
+
const Icon = KIND_ICONS[kind]
|
|
72
|
+
return {
|
|
73
|
+
id: `kind-${kind}`,
|
|
74
|
+
label: `Item ${['one', 'two', 'three'][i]}`,
|
|
75
|
+
snippet: '',
|
|
76
|
+
uri: `nostr:${kind}`,
|
|
77
|
+
icon: <Icon size={16} stroke={1.5} className="text-text-secondary" />,
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
query,
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
const kindIcon = (uri: string | null) => {
|
|
85
|
+
const kind = kindOf(uri)
|
|
86
|
+
if (!kind) return undefined
|
|
87
|
+
const Icon = KIND_ICONS[kind]
|
|
88
|
+
return <Icon size={14} stroke={1.5} />
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
const INSERT: SlashSection = {
|
|
63
92
|
label: 'Insert',
|
|
64
93
|
commands: [
|
|
@@ -67,13 +96,13 @@ const INSERT: SlashSection = {
|
|
|
67
96
|
],
|
|
68
97
|
}
|
|
69
98
|
|
|
70
|
-
function Composer({ code, sections }: { code: 'inline' | 'block'; sections?: typeof SECTIONS }) {
|
|
99
|
+
function Composer({ code, sections, kinds = false }: { code: 'inline' | 'block'; sections?: typeof SECTIONS; kinds?: boolean }) {
|
|
71
100
|
const editor = useEditor({
|
|
72
101
|
extensions: [
|
|
73
102
|
StarterKit,
|
|
74
103
|
PersonMention.configure({ people: () => PEOPLE }),
|
|
75
104
|
UrgentPersonMention.configure({ people: () => PEOPLE }),
|
|
76
|
-
CaptionedReference.configure({ icon: <IconSquareRounded size={14} stroke={1.5}
|
|
105
|
+
CaptionedReference.configure({ icon: <IconSquareRounded size={14} stroke={1.5} />, iconFor: kinds ? kindIcon : undefined }),
|
|
77
106
|
ReferenceTrigger.configure({
|
|
78
107
|
items: () => ITEMS,
|
|
79
108
|
sections,
|
|
@@ -83,7 +112,9 @@ function Composer({ code, sections }: { code: 'inline' | 'block'; sections?: typ
|
|
|
83
112
|
}),
|
|
84
113
|
SlashCommands.configure({ sections: [formatSection(code), INSERT] }),
|
|
85
114
|
],
|
|
86
|
-
content:
|
|
115
|
+
content: kinds
|
|
116
|
+
? '<p>Picked from [: <span data-captioned-reference="true" data-id="kind-circle" data-label="Item one" data-snippet="" data-uri="nostr:circle"></span> and <span data-captioned-reference="true" data-id="kind-triangle" data-label="Item two" data-snippet="" data-uri="nostr:triangle"></span>. Type [ for more: </p>'
|
|
117
|
+
: '<p>Type @, !@, [ or / after a space: </p>',
|
|
87
118
|
editorProps: { attributes: { class: richTextClassName('default', 'min-h-[80px] w-[480px] rounded-md border border-border-default p-3 outline-none') } },
|
|
88
119
|
})
|
|
89
120
|
return <EditorContent editor={editor} />
|
|
@@ -97,3 +128,11 @@ export const CodeAsABlock: Story = { render: () => <Composer code="block" /> }
|
|
|
97
128
|
|
|
98
129
|
/** `[` with the app's own groups, each headed, a row with a second line. */
|
|
99
130
|
export const ReferencesInSections: Story = { render: () => <Composer code="inline" sections={SECTIONS} /> }
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `[` offering several kinds of thing: each row brings its own icon
|
|
134
|
+
* (`CaptionedItem.icon`), and the chip it inserts wears the same one, from its
|
|
135
|
+
* `uri` (`iconFor`). A row with no icon, or a chip `iconFor` cannot name, keeps
|
|
136
|
+
* the list's and the chip's one icon.
|
|
137
|
+
*/
|
|
138
|
+
export const ReferencesOfSeveralKinds: Story = { render: () => <Composer code="inline" sections={KINDS} kinds /> }
|
|
@@ -206,6 +206,39 @@ describe('[', () => {
|
|
|
206
206
|
expect(listeners.size).toBe(0)
|
|
207
207
|
})
|
|
208
208
|
|
|
209
|
+
it('draws a row’s own icon in place of the list’s, and the chip its `iconFor` answer (7 October)', async () => {
|
|
210
|
+
const own = [
|
|
211
|
+
{ id: 'a'.repeat(64), label: 'Plan', snippet: '', uri: 'nostr:naddr1plan', icon: <svg data-icon="row-own" /> },
|
|
212
|
+
{ id: 'b'.repeat(64), label: 'Note', snippet: '', uri: 'nostr:naddr1note' },
|
|
213
|
+
]
|
|
214
|
+
function Kinds() {
|
|
215
|
+
const made = useEditor({
|
|
216
|
+
extensions: [
|
|
217
|
+
StarterKit,
|
|
218
|
+
CaptionedReference.extend({ name: 'messageMention' }).configure({
|
|
219
|
+
icon: <svg data-icon="chip-default" />,
|
|
220
|
+
iconFor: (uri) => (uri === 'nostr:naddr1plan' ? <svg data-icon="chip-own" /> : undefined),
|
|
221
|
+
}),
|
|
222
|
+
ReferenceTrigger.configure({ items: () => own, nodeName: 'messageMention', ariaLabel: 'Things', sectionLabel: 'Things', icon: <svg data-icon="list" /> }),
|
|
223
|
+
],
|
|
224
|
+
})
|
|
225
|
+
editor = made
|
|
226
|
+
return <EditorContent editor={made} />
|
|
227
|
+
}
|
|
228
|
+
render(<Kinds />)
|
|
229
|
+
await type('[')
|
|
230
|
+
const rows = within(screen.getByRole('listbox')).getAllByRole('option')
|
|
231
|
+
expect(rows[0].querySelector('[data-icon="row-own"]')).not.toBeNull()
|
|
232
|
+
expect(rows[1].querySelector('[data-icon="list"]')).not.toBeNull()
|
|
233
|
+
await press('Enter')
|
|
234
|
+
await type('[')
|
|
235
|
+
await press('ArrowDown')
|
|
236
|
+
await press('Enter')
|
|
237
|
+
const chips = document.querySelectorAll('[data-node-view-wrapper]')
|
|
238
|
+
expect(chips[0].querySelector('[data-icon="chip-own"]')).not.toBeNull()
|
|
239
|
+
expect(chips[1].querySelector('[data-icon="chip-default"]')).not.toBeNull()
|
|
240
|
+
})
|
|
241
|
+
|
|
209
242
|
it('matches who said it, or the text it searches', () => {
|
|
210
243
|
expect(filterReferences(ITEMS, 'bea').map((item) => item.id)).toEqual([ITEMS[7].id])
|
|
211
244
|
expect(filterReferences(ITEMS, '**line** 3').map((item) => item.id)).toEqual([ITEMS[3].id])
|
package/src/ComposerTriggers.tsx
CHANGED
|
@@ -229,6 +229,12 @@ export interface CaptionedItem {
|
|
|
229
229
|
search?: string
|
|
230
230
|
/** The row's second line, e.g. where the thing sits. The chip does not wear it. */
|
|
231
231
|
description?: string
|
|
232
|
+
/**
|
|
233
|
+
* This row's own 16px icon, in place of the list's `icon`: what the thing is,
|
|
234
|
+
* when the list holds several kinds — a topic, a project, a message
|
|
235
|
+
* (Katerina, 7 October). Absent, the list's icon.
|
|
236
|
+
*/
|
|
237
|
+
icon?: ReactNode
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
/** One labelled group of `[`'s rows, in the order the list draws them. */
|
|
@@ -256,6 +262,12 @@ const captionOf = (label: string, snippet: string) => (snippet ? `${label}: ${sn
|
|
|
256
262
|
export interface CaptionedReferenceOptions {
|
|
257
263
|
/** Drawn before the caption: a 14px icon. */
|
|
258
264
|
icon: ReactNode
|
|
265
|
+
/**
|
|
266
|
+
* The chip's 14px icon from what it points at — its `uri` — when chips name
|
|
267
|
+
* several kinds of thing; `undefined` from it, or no `iconFor`, draws `icon`.
|
|
268
|
+
* The same answer the list's row gave (Katerina, 7 October).
|
|
269
|
+
*/
|
|
270
|
+
iconFor?: (uri: string | null) => ReactNode | undefined
|
|
259
271
|
}
|
|
260
272
|
|
|
261
273
|
/**
|
|
@@ -270,7 +282,7 @@ export const CaptionedReference = Mention.extend<MentionOptions & CaptionedRefer
|
|
|
270
282
|
inline: true,
|
|
271
283
|
atom: true,
|
|
272
284
|
addOptions() {
|
|
273
|
-
return { ...this.parent!(), HTMLAttributes: {}, icon: null, suggestion: { char: '\0', items: () => [] } }
|
|
285
|
+
return { ...this.parent!(), HTMLAttributes: {}, icon: null, iconFor: undefined, suggestion: { char: '\0', items: () => [] } }
|
|
274
286
|
},
|
|
275
287
|
addAttributes() {
|
|
276
288
|
return {
|
|
@@ -291,11 +303,11 @@ export const CaptionedReference = Mention.extend<MentionOptions & CaptionedRefer
|
|
|
291
303
|
return [{ tag: `span[data-${this.name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}]` }]
|
|
292
304
|
},
|
|
293
305
|
addNodeView() {
|
|
294
|
-
const icon = this.options
|
|
306
|
+
const { icon, iconFor } = this.options
|
|
295
307
|
return ReactNodeViewRenderer(
|
|
296
308
|
({ node }: ReactNodeViewProps) => (
|
|
297
309
|
<NodeViewWrapper as="span" className={inlineChipClassName('neutral', 'max-w-[24ch] cursor-default')}>
|
|
298
|
-
<span className="flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary">{icon}</span>
|
|
310
|
+
<span className="flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary">{iconFor?.(node.attrs.uri ?? null) ?? icon}</span>
|
|
299
311
|
<span className="truncate">{captionOf(node.attrs.label, node.attrs.snippet)}</span>
|
|
300
312
|
</NodeViewWrapper>
|
|
301
313
|
),
|
|
@@ -309,7 +321,7 @@ export interface ReferenceMenuOptions {
|
|
|
309
321
|
ariaLabel: string
|
|
310
322
|
/** The section's heading. */
|
|
311
323
|
sectionLabel: string
|
|
312
|
-
/** At each row's start: a 16px icon. */
|
|
324
|
+
/** At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`). */
|
|
313
325
|
icon: ReactNode
|
|
314
326
|
}
|
|
315
327
|
|
|
@@ -337,7 +349,7 @@ export function referenceMenu({ ariaLabel, sectionLabel, icon }: ReferenceMenuOp
|
|
|
337
349
|
return groups
|
|
338
350
|
},
|
|
339
351
|
itemKey: (item) => `reference-${item.id}`,
|
|
340
|
-
row: (item) => ({ leading: icon, label: captionOf(item.label, item.snippet), description: item.description, hint: <EnterHint /> }),
|
|
352
|
+
row: (item) => ({ leading: item.icon ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: <EnterHint /> }),
|
|
341
353
|
}
|
|
342
354
|
}
|
|
343
355
|
|
package/src/ContainerHeader.mdx
CHANGED
|
@@ -66,7 +66,7 @@ It does these by itself. Write none of them around it.
|
|
|
66
66
|
|
|
67
67
|
| It does | So you never write | Refused when written by hand |
|
|
68
68
|
|---|---|---|
|
|
69
|
-
| Draws the column's bar: 48px tall, the title
|
|
69
|
+
| Draws the column's bar: 48px tall, the title 16px in at 14px medium, the actions 16px from the right, a hairline under it | a row of your own with padding and a border | |
|
|
70
70
|
| Keeps its 48px in a scrolling column | `shrink-0` | |
|
|
71
71
|
|
|
72
72
|
## Props
|
package/src/ContainerHeader.tsx
CHANGED
|
@@ -43,11 +43,12 @@ export function ContainerHeader({ title, chevron = false, titleMenu, actions, cl
|
|
|
43
43
|
</div>
|
|
44
44
|
)
|
|
45
45
|
return (
|
|
46
|
+
// The title 16px in, where the column's content starts (Katerina, 7 October; it was 20px).
|
|
46
47
|
// With a title menu the bar gives the button 6px of its left padding
|
|
47
|
-
// (pl-
|
|
48
|
+
// (pl-2.5 + the button's px-1.5 = pl-4): the words stay where a title's
|
|
48
49
|
// are, and the fill is inside the bar — a negative margin had it clipped
|
|
49
50
|
// (Katerina, 5 October).
|
|
50
|
-
<div className={cn('flex h-12 shrink-0 items-center justify-between overflow-hidden border-b border-border-subtle py-2 pr-4', titleMenu != null ? 'pl-
|
|
51
|
+
<div className={cn('flex h-12 shrink-0 items-center justify-between overflow-hidden border-b border-border-subtle py-2 pr-4', titleMenu != null ? 'pl-2.5' : 'pl-4', className)}>
|
|
51
52
|
<div className={cn('flex items-center gap-2 overflow-hidden', text ? 'shrink-0' : 'min-w-0 flex-1')}>
|
|
52
53
|
{titleMenu != null ? (
|
|
53
54
|
<Menu
|
package/src/DialogShell.tsx
CHANGED
|
@@ -118,7 +118,9 @@ export function DialogShell({ title, onClose, headerContent, footer, children, b
|
|
|
118
118
|
{/* Header: the package's one header bar, with the dialog's own title in it (UIG-22).
|
|
119
119
|
The title keeps to its one line and ends in "…": a long name wrapped to
|
|
120
120
|
two lines in the 48px bar and lost its top (Katerina, 1 October). */}
|
|
121
|
+
{/* `pl-5`: a dialog's title stays 20px in, over its body and footer, which are. */}
|
|
121
122
|
<ContainerHeader
|
|
123
|
+
className="pl-5"
|
|
122
124
|
title={
|
|
123
125
|
headerContent ?? (
|
|
124
126
|
<Parts.Title className="block truncate text-h4 text-text-primary" render={<span />}>
|
package/src/Kbd.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
import { cn } from './cn'
|
|
3
|
+
import { MONO_CAPTION_CLASSES } from './looks'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A keyboard hint — the small chip that names the key which does the same thing.
|
|
@@ -36,8 +37,9 @@ export function Kbd({ children, className, tone = 'default' }: KbdProps) {
|
|
|
36
37
|
return (
|
|
37
38
|
<kbd
|
|
38
39
|
className={cn(
|
|
39
|
-
'inline-flex shrink-0 items-center justify-center whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset px-1 py-px font-sans
|
|
40
|
-
|
|
40
|
+
'inline-flex shrink-0 items-center justify-center whitespace-nowrap rounded-sm border border-border-strong bg-bg-inset px-1 py-px font-sans',
|
|
41
|
+
MONO_CAPTION_CLASSES,
|
|
42
|
+
'signal:border-b-2 signal:pt-[2px] signal:pb-px signal:bg-bg-wash',
|
|
41
43
|
'ship:border-b-2 ship:pt-[2px] ship:pb-px ship:bg-bg-wash ship:font-mono ship:text-small',
|
|
42
44
|
// Leaf sets its keys in mono, as Signal does (Katerina, 28 September): the type, not the key's shape.
|
|
43
45
|
'leaf:font-mono leaf:text-small',
|
package/src/ListColumn.mdx
CHANGED
|
@@ -60,7 +60,7 @@ import { ListColumn, Toolbar, ToolbarButton } from '@estiva-app/ui'
|
|
|
60
60
|
- It sits beside the page's main area, inside AppShell's card: put both in a
|
|
61
61
|
flex row, the main area `flex-1`.
|
|
62
62
|
- The numbers: 290px wide, a 1px line on the right. The header is
|
|
63
|
-
ContainerHeader's: 48px, the title
|
|
63
|
+
ContainerHeader's: 48px, the title 16px in. The list starts 16px under the
|
|
64
64
|
header and ends 12px above the bottom, and its rows sit 12px in from each
|
|
65
65
|
side.
|
|
66
66
|
- The rows are yours. It scrolls them, so a list that grows with its data is
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks'
|
|
2
|
+
import * as MembersDialogStories from './MembersDialog.stories'
|
|
3
|
+
|
|
4
|
+
<Meta of={MembersDialogStories} />
|
|
5
|
+
|
|
6
|
+
# MembersDialog
|
|
7
|
+
|
|
8
|
+
Who is in this, with joining, leaving and adding people — the list a
|
|
9
|
+
**MembersPill** opens.
|
|
10
|
+
|
|
11
|
+
<Canvas of={MembersDialogStories.Member} />
|
|
12
|
+
|
|
13
|
+
## When
|
|
14
|
+
|
|
15
|
+
- The full roster behind a **MembersPill**: everyone in it, the viewer's own
|
|
16
|
+
Join or Leave, and "Add members" to bring somebody in.
|
|
17
|
+
|
|
18
|
+
## When not
|
|
19
|
+
|
|
20
|
+
- A glance at who is in it → **MembersPill** or **AvatarGroup**.
|
|
21
|
+
- Picking people for something that is not membership → **ChipInput** in your own **DialogShell**.
|
|
22
|
+
|
|
23
|
+
## How
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { MembersDialog } from '@estiva-app/ui'
|
|
27
|
+
|
|
28
|
+
const roster: { id: string; name: string; picture?: string; caption?: string }[] = []
|
|
29
|
+
const directory: { id: string; label: string; picture?: string }[] = []
|
|
30
|
+
const [open, setOpen] = useState(false)
|
|
31
|
+
|
|
32
|
+
{open && (
|
|
33
|
+
<MembersDialog
|
|
34
|
+
members={roster}
|
|
35
|
+
self={{ action: 'join', onToggle: () => {} }}
|
|
36
|
+
candidates={directory}
|
|
37
|
+
onAdd={(chosen) => {}}
|
|
38
|
+
onClose={() => setOpen(false)}
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- A row is `{ id, name, picture?, caption? }`. The caption is the second
|
|
44
|
+
line: a role, a relation such as "Assignee", or a state. Name the viewer's
|
|
45
|
+
own row "You".
|
|
46
|
+
- `self` puts **Join** or **Leave** on top. Leave it out while membership is
|
|
47
|
+
still being read: no row is better than a wrong one. The row waits while
|
|
48
|
+
`onToggle`'s promise runs.
|
|
49
|
+
- `onAdd` shows **Add members**, which opens the add layer: an "Invite people"
|
|
50
|
+
**ChipInput** over `candidates`, leaving out anyone already a member by id.
|
|
51
|
+
Return `false` to stay on the layer; otherwise it returns to the roster.
|
|
52
|
+
- `count` is the number in the title when it differs from the rows — a row
|
|
53
|
+
for somebody who left but still holds a role, say.
|
|
54
|
+
- `note` is one line under the roster, for what membership does not cover.
|
|
55
|
+
|
|
56
|
+
## What it owns
|
|
57
|
+
|
|
58
|
+
It does these by itself, on top of what **DialogShell** and **ChipInput** own.
|
|
59
|
+
What a join, a leave or an add does is yours.
|
|
60
|
+
|
|
61
|
+
| It does | So you never write | Refused when written by hand |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| Goes between the roster and the add layer, and back after an add | a view state, or a second dialog | |
|
|
64
|
+
| Waits on the Join or Leave row while `onToggle` runs, so one press is one change | a busy flag around it | |
|
|
65
|
+
| Leaves current members out of the picker | a filter over your directory | |
|
|
66
|
+
|
|
67
|
+
## Props
|
|
68
|
+
|
|
69
|
+
<Controls of={MembersDialogStories.Member} />
|