@syscore/ui-library 1.15.3 → 1.16.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/client/components/icons/UtilityCompare.tsx +37 -35
- package/client/components/icons/UtilityFeedback.tsx +30 -0
- package/client/components/icons/UtilityRevisionsHide.tsx +1 -1
- package/client/components/icons/UtilityRevisionsShow.tsx +40 -0
- package/client/components/icons/imperative-badges/BadgeImperativePrimary.tsx +45 -0
- package/client/components/icons/imperative-badges/BadgeImperativeSecondary.tsx +53 -0
- package/client/components/icons/imperative-badges/index.tsx +2 -0
- package/client/components/ui/mobile-nav.tsx +278 -0
- package/client/components/ui/tooltip.tsx +39 -20
- package/client/global.css +104 -7
- package/client/ui/Icons.stories.tsx +52 -30
- package/client/ui/MobileNav.stories.tsx +317 -0
- package/client/ui/PageHeader.stories.tsx +82 -0
- package/client/ui/Tooltip.stories.tsx +38 -30
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +24 -0
- package/dist/index.es.js +449 -41
- package/package.json +1 -1
package/client/global.css
CHANGED
|
@@ -2042,7 +2042,6 @@ body {
|
|
|
2042
2042
|
/* Tooltip Styles */
|
|
2043
2043
|
.tooltip-content {
|
|
2044
2044
|
position: relative;
|
|
2045
|
-
background-color: #3e4049;
|
|
2046
2045
|
z-index: 50;
|
|
2047
2046
|
border-radius: 0.5rem;
|
|
2048
2047
|
padding: 1.5rem;
|
|
@@ -2055,6 +2054,20 @@ body {
|
|
|
2055
2054
|
zoom-in 0.15s ease-in-out;
|
|
2056
2055
|
}
|
|
2057
2056
|
|
|
2057
|
+
.tooltip-content--default {
|
|
2058
|
+
background-color: #3e4049;
|
|
2059
|
+
color: #39c9ea;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
.tooltip-content--simple {
|
|
2063
|
+
border-radius: var(--radius-full, 9999px);
|
|
2064
|
+
background-color: var(--color-cyan-50, #f0f8fb);
|
|
2065
|
+
border: 1px solid var(--color-cyan-100, #eff5fb);
|
|
2066
|
+
color: inherit;
|
|
2067
|
+
padding: 0.75rem 1rem;
|
|
2068
|
+
margin-top: 0.5rem;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2058
2071
|
.tooltip-content[data-state="closed"] {
|
|
2059
2072
|
animation:
|
|
2060
2073
|
fade-out 0.15s ease-in-out,
|
|
@@ -3773,7 +3786,7 @@ body {
|
|
|
3773
3786
|
position: fixed;
|
|
3774
3787
|
inset: 0;
|
|
3775
3788
|
z-index: 50;
|
|
3776
|
-
background-color: rgba(0, 0, 0, 0.
|
|
3789
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
3777
3790
|
}
|
|
3778
3791
|
|
|
3779
3792
|
.drawer-content {
|
|
@@ -3785,9 +3798,10 @@ body {
|
|
|
3785
3798
|
margin-top: 6rem;
|
|
3786
3799
|
display: flex;
|
|
3787
3800
|
height: auto;
|
|
3801
|
+
max-height: 70vh;
|
|
3788
3802
|
flex-direction: column;
|
|
3789
|
-
border-top-left-radius:
|
|
3790
|
-
border-top-right-radius:
|
|
3803
|
+
border-top-left-radius: 32px;
|
|
3804
|
+
border-top-right-radius: 32px;
|
|
3791
3805
|
border: 1px solid hsl(var(--border));
|
|
3792
3806
|
background-color: hsl(var(--background));
|
|
3793
3807
|
}
|
|
@@ -3796,10 +3810,10 @@ body {
|
|
|
3796
3810
|
margin-left: auto;
|
|
3797
3811
|
margin-right: auto;
|
|
3798
3812
|
margin-top: 1rem;
|
|
3799
|
-
height:
|
|
3800
|
-
width:
|
|
3813
|
+
height: 4px;
|
|
3814
|
+
width: 52px;
|
|
3801
3815
|
border-radius: 9999px;
|
|
3802
|
-
background-color:
|
|
3816
|
+
background-color: var(--color-gray-300);
|
|
3803
3817
|
}
|
|
3804
3818
|
|
|
3805
3819
|
.drawer-header {
|
|
@@ -3836,6 +3850,89 @@ body {
|
|
|
3836
3850
|
color: hsl(var(--muted-foreground));
|
|
3837
3851
|
}
|
|
3838
3852
|
|
|
3853
|
+
/* MobileNav Styles */
|
|
3854
|
+
.mobile-nav {
|
|
3855
|
+
display: flex;
|
|
3856
|
+
flex-direction: column;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
.mobile-nav-panel {
|
|
3860
|
+
background-color: white;
|
|
3861
|
+
border-radius: 32px 32px 0 0;
|
|
3862
|
+
box-shadow:
|
|
3863
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
3864
|
+
0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
|
3865
|
+
overflow: hidden;
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
.mobile-nav-panel[data-closed] {
|
|
3869
|
+
border-top: 0;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
.mobile-nav-handle {
|
|
3873
|
+
display: flex;
|
|
3874
|
+
justify-content: center;
|
|
3875
|
+
padding-top: 1rem;
|
|
3876
|
+
padding-bottom: 2rem;
|
|
3877
|
+
cursor: grab;
|
|
3878
|
+
touch-action: none;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
.mobile-nav-handle:active {
|
|
3882
|
+
cursor: grabbing;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
.mobile-nav-handle-bar {
|
|
3886
|
+
height: 4px;
|
|
3887
|
+
width: 52px;
|
|
3888
|
+
border-radius: 9999px;
|
|
3889
|
+
background-color: var(--color-gray-300);
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
.mobile-nav-content {
|
|
3893
|
+
overflow-y: auto;
|
|
3894
|
+
height: calc(100% - 40px);
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
.mobile-nav-bar {
|
|
3898
|
+
border-top: 1px solid var(--color-gray-100);
|
|
3899
|
+
background-color: var(--color-gray-50);
|
|
3900
|
+
flex-shrink: 0;
|
|
3901
|
+
min-height: 136px;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
.mobile-nav-bar-inner {
|
|
3905
|
+
margin-left: auto;
|
|
3906
|
+
margin-right: auto;
|
|
3907
|
+
display: flex;
|
|
3908
|
+
max-width: 28rem;
|
|
3909
|
+
align-items: center;
|
|
3910
|
+
justify-content: space-around;
|
|
3911
|
+
padding-top: 0.5rem;
|
|
3912
|
+
padding-bottom: 2rem;
|
|
3913
|
+
padding-left: 1.5rem;
|
|
3914
|
+
padding-right: 1.5rem;
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
.mobile-nav-trigger {
|
|
3918
|
+
display: flex;
|
|
3919
|
+
align-items: center;
|
|
3920
|
+
justify-content: center;
|
|
3921
|
+
width: 64px;
|
|
3922
|
+
height: 64px;
|
|
3923
|
+
cursor: pointer;
|
|
3924
|
+
transition: color 150ms;
|
|
3925
|
+
color: var(--color-gray-400);
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
.mobile-nav-trigger:hover {
|
|
3929
|
+
color: var(--color-gray-600);
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
.mobile-nav-trigger[data-active] {
|
|
3933
|
+
color: var(--color-gray-900);
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3839
3936
|
/* Pagination Styles */
|
|
3840
3937
|
.pagination {
|
|
3841
3938
|
margin-left: auto;
|
|
@@ -72,6 +72,10 @@ import {
|
|
|
72
72
|
SealProviders,
|
|
73
73
|
} from "../components/icons/provider-seals";
|
|
74
74
|
import { UtilityTrash } from "@/components/icons/UtilityTrash";
|
|
75
|
+
import {
|
|
76
|
+
BadgeImperativePrimary,
|
|
77
|
+
BadgeImperativeSecondary,
|
|
78
|
+
} from "../components/icons/imperative-badges";
|
|
75
79
|
import {
|
|
76
80
|
BadgeCertificationBronze,
|
|
77
81
|
BadgeCertificationSilver,
|
|
@@ -323,58 +327,58 @@ export const Seals: Story = {
|
|
|
323
327
|
<h2 className="text-2xl font-bold mb-6">Seals</h2>
|
|
324
328
|
<div className="grid grid-cols-1 gap-6">
|
|
325
329
|
<div className="grid grid-cols-2 sm:grid-cols-6 items-start gap-6">
|
|
326
|
-
<div className="flex flex-col items-
|
|
330
|
+
<div className="flex flex-col items-center gap-2">
|
|
327
331
|
<p>IWBILogo</p>
|
|
328
332
|
<div className="flex flex-col items-center gap-2">
|
|
329
|
-
<IWBILogo className="size-
|
|
333
|
+
<IWBILogo className="size-24" />
|
|
330
334
|
</div>
|
|
331
335
|
</div>
|
|
332
336
|
<div className="flex flex-col items-center gap-2">
|
|
333
337
|
<p>WELL Seals</p>
|
|
334
|
-
<div className="flex flex-col gap-2">
|
|
335
|
-
<SealWell className="size-
|
|
336
|
-
<SealWorksWithWell className="size-
|
|
338
|
+
<div className="flex flex-col items-center gap-2">
|
|
339
|
+
<SealWell className="size-24" />
|
|
340
|
+
<SealWorksWithWell className="size-24" />
|
|
337
341
|
</div>
|
|
338
342
|
</div>
|
|
339
343
|
<div className="flex flex-col items-center gap-2">
|
|
340
344
|
<p>IWBI Seals</p>
|
|
341
|
-
<div className="flex flex-col gap-2">
|
|
342
|
-
<SealIwbiMember className="size-
|
|
343
|
-
<SealWellCertification className="size-
|
|
344
|
-
<SealWellCommunity className="size-
|
|
345
|
-
<SealWellResidence className="size-
|
|
345
|
+
<div className="flex flex-col items-center gap-2">
|
|
346
|
+
<SealIwbiMember className="size-24" />
|
|
347
|
+
<SealWellCertification className="size-24" />
|
|
348
|
+
<SealWellCommunity className="size-24" />
|
|
349
|
+
<SealWellResidence className="size-24" />
|
|
346
350
|
</div>
|
|
347
351
|
</div>
|
|
348
352
|
<div className="flex flex-col items-center gap-2">
|
|
349
353
|
<p>Certification Seals</p>
|
|
350
|
-
<div className="flex flex-col gap-2">
|
|
351
|
-
<SealCertification className="size-
|
|
352
|
-
<SealCertificationBronze className="size-
|
|
353
|
-
<SealCertificationSilver className="size-
|
|
354
|
-
<SealCertificationGold className="size-
|
|
355
|
-
<SealCertificationPlatinum className="size-
|
|
354
|
+
<div className="flex flex-col items-center justify-center gap-2">
|
|
355
|
+
<SealCertification className="size-24" />
|
|
356
|
+
<SealCertificationBronze className="size-24" />
|
|
357
|
+
<SealCertificationSilver className="size-24" />
|
|
358
|
+
<SealCertificationGold className="size-24" />
|
|
359
|
+
<SealCertificationPlatinum className="size-24" />
|
|
356
360
|
</div>
|
|
357
361
|
</div>
|
|
358
362
|
<div className="flex flex-col items-center gap-2">
|
|
359
363
|
<p>Rating Seals</p>
|
|
360
|
-
<div className="flex flex-col gap-2">
|
|
361
|
-
<SealRating className="size-
|
|
362
|
-
<SealRatingHealthSafety className="size-
|
|
363
|
-
<SealRatingOperations className="size-
|
|
364
|
-
<SealRatingPerformance className="size-
|
|
365
|
-
<SealRatingEquity className="size-
|
|
366
|
-
<SealRatingCoworking className="size-
|
|
367
|
-
<SealRatingWorkforce className="size-
|
|
368
|
-
<SealRatingDesign className="size-
|
|
369
|
-
<SealRatingRealEstate className="size-
|
|
364
|
+
<div className="flex flex-col items-center justify-center gap-2">
|
|
365
|
+
<SealRating className="size-24" />
|
|
366
|
+
<SealRatingHealthSafety className="size-24" />
|
|
367
|
+
<SealRatingOperations className="size-24" />
|
|
368
|
+
<SealRatingPerformance className="size-24" />
|
|
369
|
+
<SealRatingEquity className="size-24" />
|
|
370
|
+
<SealRatingCoworking className="size-24" />
|
|
371
|
+
<SealRatingWorkforce className="size-24" />
|
|
372
|
+
<SealRatingDesign className="size-24" />
|
|
373
|
+
<SealRatingRealEstate className="size-24" />
|
|
370
374
|
</div>
|
|
371
375
|
</div>
|
|
372
376
|
<div className="flex flex-col items-center gap-2">
|
|
373
377
|
<p>Watermark Seals</p>
|
|
374
|
-
<div className="flex flex-col gap-2">
|
|
375
|
-
<WatermarkMemberOrg className="size-
|
|
376
|
-
<WaterMarkWWWProducts className="size-
|
|
377
|
-
<WaterMarkWellProjects className="size-
|
|
378
|
+
<div className="flex flex-col items-center justify-center gap-2">
|
|
379
|
+
<WatermarkMemberOrg className="size-24" />
|
|
380
|
+
<WaterMarkWWWProducts className="size-24" />
|
|
381
|
+
<WaterMarkWellProjects className="size-24" />
|
|
378
382
|
</div>
|
|
379
383
|
</div>
|
|
380
384
|
</div>
|
|
@@ -477,3 +481,21 @@ export const AchievementBadges: Story = {
|
|
|
477
481
|
</div>
|
|
478
482
|
),
|
|
479
483
|
};
|
|
484
|
+
|
|
485
|
+
export const ImperativeBadges: Story = {
|
|
486
|
+
render: () => (
|
|
487
|
+
<div className="space-y-8 p-8">
|
|
488
|
+
<h2 className="text-2xl font-bold mb-6">Imperative Badges</h2>
|
|
489
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
490
|
+
<div className="flex flex-col items-center gap-2">
|
|
491
|
+
<BadgeImperativePrimary />
|
|
492
|
+
<span className="text-sm font-medium">Primary</span>
|
|
493
|
+
</div>
|
|
494
|
+
<div className="flex flex-col items-center gap-2">
|
|
495
|
+
<BadgeImperativeSecondary />
|
|
496
|
+
<span className="text-sm font-medium">Secondary</span>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
),
|
|
501
|
+
};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
MobileNav,
|
|
4
|
+
MobileNavPanel,
|
|
5
|
+
MobileNavBar,
|
|
6
|
+
MobileNavTrigger,
|
|
7
|
+
} from "../components/ui/mobile-nav";
|
|
8
|
+
import {
|
|
9
|
+
Workflow,
|
|
10
|
+
PenLine,
|
|
11
|
+
MessageSquare,
|
|
12
|
+
TriangleAlert,
|
|
13
|
+
X,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import { useState } from "react";
|
|
16
|
+
import { UtilityText } from "../components/icons/UtilityText";
|
|
17
|
+
import { UtilityCompare } from "../components/icons/UtilityCompare";
|
|
18
|
+
import { UtilityRevisionsShow } from "../components/icons/UtilityRevisionsShow";
|
|
19
|
+
import { UtilityFeedback } from "../components/icons/UtilityFeedback";
|
|
20
|
+
|
|
21
|
+
const meta = {
|
|
22
|
+
title: "Review/MobileNav",
|
|
23
|
+
component: MobileNav,
|
|
24
|
+
tags: ["autodocs"],
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: "fullscreen",
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component: `
|
|
30
|
+
Mobile navigation with a spring-animated slide-up panel. Built as a **compound component** — state is managed internally via React context, so consumers never need \`useState\` or handlers.
|
|
31
|
+
|
|
32
|
+
## Compound Components
|
|
33
|
+
|
|
34
|
+
| Component | Role |
|
|
35
|
+
|---|---|
|
|
36
|
+
| \`MobileNav\` | Root — holds state, provides context |
|
|
37
|
+
| \`MobileNavPanel\` | Slide-up panel with 3 discrete states: **initial** (content height, max 70%), **full** (viewport), **closed** |
|
|
38
|
+
| \`MobileNavBar\` | Bottom nav bar wrapper |
|
|
39
|
+
| \`MobileNavTrigger\` | Individual nav button — toggles the panel or fires a custom action |
|
|
40
|
+
|
|
41
|
+
## Basic Usage
|
|
42
|
+
|
|
43
|
+
\`\`\`tsx
|
|
44
|
+
<MobileNav className="h-screen">
|
|
45
|
+
<MobileNavPanel>
|
|
46
|
+
{(activeKey) => <MyContent tab={activeKey} />}
|
|
47
|
+
</MobileNavPanel>
|
|
48
|
+
|
|
49
|
+
<MobileNavBar>
|
|
50
|
+
<MobileNavTrigger value="home" label="Home">
|
|
51
|
+
<HomeIcon className="size-6" />
|
|
52
|
+
</MobileNavTrigger>
|
|
53
|
+
<MobileNavTrigger value="search" label="Search">
|
|
54
|
+
<SearchIcon className="size-6" />
|
|
55
|
+
</MobileNavTrigger>
|
|
56
|
+
</MobileNavBar>
|
|
57
|
+
</MobileNav>
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
## Styling Active State
|
|
61
|
+
|
|
62
|
+
\`MobileNavTrigger\` renders a \`<button>\` with the \`group\` class and a \`data-active\` attribute when selected. Use Tailwind's \`group-data-active:\` modifier on children to style based on active state:
|
|
63
|
+
|
|
64
|
+
\`\`\`tsx
|
|
65
|
+
<MobileNavTrigger value="home" label="Home">
|
|
66
|
+
<HomeIcon className="size-6 text-gray-400 group-data-active:text-gray-900" />
|
|
67
|
+
<span className="text-xs hidden group-data-active:block">Home</span>
|
|
68
|
+
</MobileNavTrigger>
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
## Custom Actions (onAction)
|
|
72
|
+
|
|
73
|
+
Use \`onAction\` on a trigger to fire a custom callback instead of opening the panel:
|
|
74
|
+
|
|
75
|
+
\`\`\`tsx
|
|
76
|
+
<MobileNavTrigger value="add" label="Add" onAction={() => openModal()}>
|
|
77
|
+
<PlusIcon className="size-6" />
|
|
78
|
+
</MobileNavTrigger>
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
## Panel Gestures
|
|
82
|
+
|
|
83
|
+
The drag handle supports swipe gestures that snap between discrete states:
|
|
84
|
+
|
|
85
|
+
- **Swipe up** from initial → full screen
|
|
86
|
+
- **Swipe down** from full → initial height
|
|
87
|
+
- **Swipe down** from initial → close
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
} satisfies Meta<typeof MobileNav>;
|
|
93
|
+
|
|
94
|
+
export default meta;
|
|
95
|
+
|
|
96
|
+
type Story = StoryObj<typeof meta>;
|
|
97
|
+
|
|
98
|
+
type TabKey = "list" | "workflow" | "edit" | "comments";
|
|
99
|
+
|
|
100
|
+
const tabContent: Record<
|
|
101
|
+
TabKey,
|
|
102
|
+
{ title: string; items: { label: string; description: string }[] }
|
|
103
|
+
> = {
|
|
104
|
+
list: {
|
|
105
|
+
title: "Changes",
|
|
106
|
+
items: [
|
|
107
|
+
{
|
|
108
|
+
label: "Reduced scope",
|
|
109
|
+
description:
|
|
110
|
+
"Requirement 2 no longer includes sulfide as a testing parameter.",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: "Clarity edits",
|
|
114
|
+
description: "Requirement 2 has been re-written to maximize clarity.",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: "New threshold",
|
|
118
|
+
description:
|
|
119
|
+
"Requirement 3 adds a 0.05 mg/L threshold for lead in drinking water.",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
workflow: {
|
|
124
|
+
title: "Workflow",
|
|
125
|
+
items: [
|
|
126
|
+
{
|
|
127
|
+
label: "Review pending",
|
|
128
|
+
description: "3 items awaiting team review before finalization.",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: "Approved",
|
|
132
|
+
description: "12 strategies have been approved this cycle.",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
edit: {
|
|
137
|
+
title: "Edits",
|
|
138
|
+
items: [
|
|
139
|
+
{
|
|
140
|
+
label: "Draft update",
|
|
141
|
+
description: "Strategy C8.2 has been revised with new language.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: "Formatting fix",
|
|
145
|
+
description:
|
|
146
|
+
"Table headers in Requirement 1 corrected for consistency.",
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
comments: {
|
|
151
|
+
title: "Comments",
|
|
152
|
+
items: [
|
|
153
|
+
{
|
|
154
|
+
label: "Team feedback",
|
|
155
|
+
description:
|
|
156
|
+
'Sarah noted: "Consider adding a grace period for compliance."',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: "Compliance window",
|
|
160
|
+
description:
|
|
161
|
+
"Suggest extending the compliance window from 30 to 60 days.",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: "Testing methodology",
|
|
165
|
+
description:
|
|
166
|
+
"Lab results should include both grab and composite samples.",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: "Documentation gap",
|
|
170
|
+
description:
|
|
171
|
+
"Section 4.2 is missing referenced appendix for field procedures.",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: "Stakeholder input",
|
|
175
|
+
description:
|
|
176
|
+
"Community advisory board requested public comment period extension.",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
label: "Budget concern",
|
|
180
|
+
description:
|
|
181
|
+
"Monitoring costs may exceed allocated budget by 15% in Q3.",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: "Reviewer note",
|
|
185
|
+
description:
|
|
186
|
+
"External reviewer flagged Requirement 4 for further clarification.",
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const PanelContent = ({ activeKey }: { activeKey: string }) => {
|
|
193
|
+
const content = tabContent[activeKey as TabKey];
|
|
194
|
+
if (!content) return null;
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className="mx-auto w-full max-w-md">
|
|
198
|
+
<div className="px-4 pb-2">
|
|
199
|
+
<h2 className="text-lg font-semibold tracking-tight">
|
|
200
|
+
{content.title}
|
|
201
|
+
</h2>
|
|
202
|
+
</div>
|
|
203
|
+
<div className="px-4 pb-4 space-y-3">
|
|
204
|
+
{content.items.map((item) => (
|
|
205
|
+
<div
|
|
206
|
+
key={item.label}
|
|
207
|
+
className="flex items-start justify-between gap-3 rounded-lg border border-gray-100 bg-white p-4"
|
|
208
|
+
>
|
|
209
|
+
<div className="space-y-1">
|
|
210
|
+
<p className="text-sm font-semibold text-gray-900">
|
|
211
|
+
{item.label}
|
|
212
|
+
</p>
|
|
213
|
+
<p className="text-sm text-gray-500">{item.description}</p>
|
|
214
|
+
</div>
|
|
215
|
+
<TriangleAlert className="mt-0.5 size-5 shrink-0 text-teal-400" />
|
|
216
|
+
</div>
|
|
217
|
+
))}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const Default: Story = {
|
|
224
|
+
args: { children: null },
|
|
225
|
+
|
|
226
|
+
render: () => (
|
|
227
|
+
<MobileNav className="h-screen w-full bg-gray-50">
|
|
228
|
+
{/* Main content area */}
|
|
229
|
+
<div className="flex-1 p-6 overflow-y-auto">
|
|
230
|
+
<p className="text-sm text-gray-400">
|
|
231
|
+
Tap an icon below to open the panel.
|
|
232
|
+
</p>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Panel slides up above the nav */}
|
|
236
|
+
<MobileNavPanel>
|
|
237
|
+
{(activeKey) => <PanelContent activeKey={activeKey} />}
|
|
238
|
+
</MobileNavPanel>
|
|
239
|
+
|
|
240
|
+
{/* Nav — always visible */}
|
|
241
|
+
<MobileNavBar>
|
|
242
|
+
<MobileNavTrigger value="list" label="Changes">
|
|
243
|
+
<UtilityText className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
244
|
+
</MobileNavTrigger>
|
|
245
|
+
<MobileNavTrigger value="workflow" label="Workflow">
|
|
246
|
+
<UtilityCompare className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
247
|
+
</MobileNavTrigger>
|
|
248
|
+
<MobileNavTrigger value="edit" label="Edits">
|
|
249
|
+
<UtilityRevisionsShow className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
250
|
+
</MobileNavTrigger>
|
|
251
|
+
<MobileNavTrigger value="comments" label="Comments">
|
|
252
|
+
<MessageSquare className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
253
|
+
</MobileNavTrigger>
|
|
254
|
+
</MobileNavBar>
|
|
255
|
+
</MobileNav>
|
|
256
|
+
),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const WithCustomActionRender = () => {
|
|
260
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<MobileNav className="relative h-screen w-full bg-gray-50">
|
|
264
|
+
<div className="flex-1 p-6 overflow-y-auto">
|
|
265
|
+
<p className="text-sm text-gray-400">
|
|
266
|
+
The “Edits” trigger opens a modal instead of the panel.
|
|
267
|
+
</p>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<MobileNavPanel>
|
|
271
|
+
{(activeKey) => <PanelContent activeKey={activeKey} />}
|
|
272
|
+
</MobileNavPanel>
|
|
273
|
+
|
|
274
|
+
<MobileNavBar>
|
|
275
|
+
<MobileNavTrigger value="list" label="Changes">
|
|
276
|
+
<UtilityText className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
277
|
+
</MobileNavTrigger>
|
|
278
|
+
<MobileNavTrigger value="workflow" label="Workflow">
|
|
279
|
+
<UtilityCompare className="size-8 text-gray-500 group-data-active:text-gray-900" />
|
|
280
|
+
</MobileNavTrigger>
|
|
281
|
+
<MobileNavTrigger
|
|
282
|
+
value="edit"
|
|
283
|
+
label="Edits"
|
|
284
|
+
onAction={() => setModalOpen(true)}
|
|
285
|
+
>
|
|
286
|
+
<UtilityRevisionsShow className="size-6 text-gray-500" />
|
|
287
|
+
</MobileNavTrigger>
|
|
288
|
+
<MobileNavTrigger value="comments" label="Comments">
|
|
289
|
+
<UtilityFeedback className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
290
|
+
</MobileNavTrigger>
|
|
291
|
+
</MobileNavBar>
|
|
292
|
+
|
|
293
|
+
{modalOpen && (
|
|
294
|
+
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/20">
|
|
295
|
+
<div className="relative rounded-xl bg-white p-6 shadow-lg w-72">
|
|
296
|
+
<button
|
|
297
|
+
onClick={() => setModalOpen(false)}
|
|
298
|
+
className="absolute top-3 right-3 text-gray-400 hover:text-gray-600"
|
|
299
|
+
aria-label="Close"
|
|
300
|
+
>
|
|
301
|
+
<X className="size-4" />
|
|
302
|
+
</button>
|
|
303
|
+
<p className="text-sm font-semibold text-gray-900">Edit mode</p>
|
|
304
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
305
|
+
This modal was opened via onAction instead of the panel.
|
|
306
|
+
</p>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
</MobileNav>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export const WithCustomAction: Story = {
|
|
315
|
+
args: { children: null },
|
|
316
|
+
render: () => <WithCustomActionRender />,
|
|
317
|
+
};
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
} from "../components/ui/tooltip";
|
|
17
17
|
import { motion, AnimatePresence } from "motion/react";
|
|
18
18
|
import { useState } from "react";
|
|
19
|
+
import { BadgeImperativePrimary } from "@/components/icons/imperative-badges";
|
|
20
|
+
import { Text } from "@/components/ui/typography";
|
|
19
21
|
|
|
20
22
|
const meta = {
|
|
21
23
|
title: "Review/PageHeader",
|
|
@@ -288,3 +290,83 @@ export const ThemeHeaderWithPoints: Story = {
|
|
|
288
290
|
);
|
|
289
291
|
},
|
|
290
292
|
};
|
|
293
|
+
|
|
294
|
+
export const ThemeHeaderWithImperativeBadge: Story = {
|
|
295
|
+
render: () => {
|
|
296
|
+
const themeCode = "C8";
|
|
297
|
+
const conceptColor = {
|
|
298
|
+
solid: "#0f748a",
|
|
299
|
+
contrast: "#0f748a",
|
|
300
|
+
border: "#0f748a",
|
|
301
|
+
};
|
|
302
|
+
const themeData = {
|
|
303
|
+
name: "Occupant experience",
|
|
304
|
+
description:
|
|
305
|
+
"This theme focuses on understanding and improving the overall experience of building occupants through research and feedback mechanisms.",
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
309
|
+
const [tooltipOpen, setTooltipOpen] = useState(false);
|
|
310
|
+
const isVisible = isHovered || tooltipOpen;
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<PageHeader>
|
|
314
|
+
<PageHeaderTopSection className="mb-4">
|
|
315
|
+
<PageHeaderLeftContent>
|
|
316
|
+
<Tag
|
|
317
|
+
variant="code"
|
|
318
|
+
style={{
|
|
319
|
+
backgroundColor: conceptColor.contrast || conceptColor.solid,
|
|
320
|
+
borderColor: conceptColor.border,
|
|
321
|
+
color: "white",
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
{themeCode}
|
|
325
|
+
</Tag>
|
|
326
|
+
<Tooltip trigger="click">
|
|
327
|
+
<TooltipTrigger>
|
|
328
|
+
<span className="body-base text-gray-600 underline-dotted cursor-pointer">
|
|
329
|
+
Theme
|
|
330
|
+
</span>
|
|
331
|
+
</TooltipTrigger>
|
|
332
|
+
<TooltipContent className="max-w-[400px]">
|
|
333
|
+
<p className="body-base text-white">
|
|
334
|
+
Themes are groupings of related strategies that address
|
|
335
|
+
specific aspects of health and well-being within a concept
|
|
336
|
+
area.
|
|
337
|
+
</p>
|
|
338
|
+
</TooltipContent>
|
|
339
|
+
</Tooltip>
|
|
340
|
+
</PageHeaderLeftContent>
|
|
341
|
+
|
|
342
|
+
<div className="flex items-center gap-4">
|
|
343
|
+
<Tooltip>
|
|
344
|
+
<TooltipTrigger>
|
|
345
|
+
<BadgeImperativePrimary className="cursor-pointer" />
|
|
346
|
+
</TooltipTrigger>
|
|
347
|
+
<TooltipContent variant="simple">
|
|
348
|
+
<Text as="p" variant="body-small">
|
|
349
|
+
<span className="font-semibold">Required</span> for
|
|
350
|
+
certification
|
|
351
|
+
</Text>
|
|
352
|
+
</TooltipContent>
|
|
353
|
+
</Tooltip>
|
|
354
|
+
<div className="flex items-end gap-1">
|
|
355
|
+
<span className="text-gray-800 number-large font-semibold">
|
|
356
|
+
1
|
|
357
|
+
</span>
|
|
358
|
+
<span className="text-gray-800 overline-medium">PT</span>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</PageHeaderTopSection>
|
|
362
|
+
|
|
363
|
+
<PageHeaderTitle className="heading-small mb-3">
|
|
364
|
+
{themeData.name}
|
|
365
|
+
</PageHeaderTitle>
|
|
366
|
+
<PageHeaderDescription className="body-large">
|
|
367
|
+
{themeData.description}
|
|
368
|
+
</PageHeaderDescription>
|
|
369
|
+
</PageHeader>
|
|
370
|
+
);
|
|
371
|
+
},
|
|
372
|
+
};
|