@syscore/ui-library 1.8.0 → 1.9.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.
@@ -0,0 +1,430 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import {
3
+ Accordion,
4
+ AccordionItem,
5
+ AccordionContent,
6
+ AccordionContainer,
7
+ AccordionSectionHeader,
8
+ AccordionHeader,
9
+ AccordionTrigger,
10
+ AccordionListRow,
11
+ } from "@/components/ui/accordion";
12
+ import { Tag } from "@/components/ui/tag";
13
+ import { useState } from "react";
14
+ import { concepts } from "@/lib/concepts-mock-data";
15
+ import { getConceptColor } from "@/lib/concept-colors";
16
+ import { CONCEPT_ICONS } from "@/lib/concept-icons";
17
+ import { Text } from "@/components/ui/typography";
18
+
19
+ const meta = {
20
+ title: "Review/Accordion",
21
+ component: Accordion,
22
+ tags: ["autodocs"],
23
+ parameters: {
24
+ layout: "fullscreen",
25
+ },
26
+ } satisfies Meta<typeof Accordion>;
27
+
28
+ export default meta;
29
+
30
+ type Story = StoryObj<typeof meta>;
31
+
32
+ // Generic unstyled example
33
+ export const Default: Story = {
34
+ render: () => {
35
+ const [expanded, setExpanded] = useState<Set<string>>(new Set());
36
+
37
+ return (
38
+ <div className="p-8 max-w-2xl">
39
+ <Accordion
40
+ allowMultiple={false}
41
+ expandedValues={expanded}
42
+ onExpandedChange={setExpanded}
43
+ className="border rounded-lg overflow-hidden"
44
+ >
45
+ <AccordionItem value="item-1" className="border-b last:border-b-0">
46
+ <AccordionHeader className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
47
+ <span className="font-medium">Is it accessible?</span>
48
+ <AccordionTrigger />
49
+ </AccordionHeader>
50
+ <AccordionContent className="overflow-hidden">
51
+ <div className="p-4 pt-0">
52
+ Yes. It adheres to the WAI-ARIA design pattern and includes
53
+ smooth animations powered by Motion.
54
+ </div>
55
+ </AccordionContent>
56
+ </AccordionItem>
57
+
58
+ <AccordionItem value="item-2" className="border-b last:border-b-0">
59
+ <AccordionHeader className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
60
+ <span className="font-medium">Is it animated?</span>
61
+ <AccordionTrigger />
62
+ </AccordionHeader>
63
+ <AccordionContent className="overflow-hidden">
64
+ <div className="p-4 pt-0">
65
+ Yes. It uses Motion/React for smooth height and opacity
66
+ animations.
67
+ </div>
68
+ </AccordionContent>
69
+ </AccordionItem>
70
+
71
+ <AccordionItem value="item-3" className="border-b last:border-b-0">
72
+ <AccordionHeader className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
73
+ <span className="font-medium">Can multiple items be open?</span>
74
+ <AccordionTrigger />
75
+ </AccordionHeader>
76
+ <AccordionContent className="overflow-hidden">
77
+ <div className="p-4 pt-0">
78
+ Yes. Set <code>allowMultiple=true</code> to allow multiple items
79
+ to be expanded at once.
80
+ </div>
81
+ </AccordionContent>
82
+ </AccordionItem>
83
+ </Accordion>
84
+ </div>
85
+ );
86
+ },
87
+ };
88
+
89
+ // Multiple selection example
90
+ export const MultipleSelection: Story = {
91
+ render: () => {
92
+ const [expanded, setExpanded] = useState<Set<string>>(new Set());
93
+
94
+ return (
95
+ <div className="p-8 max-w-2xl">
96
+ <Accordion
97
+ allowMultiple={true}
98
+ expandedValues={expanded}
99
+ onExpandedChange={setExpanded}
100
+ className="border rounded-lg overflow-hidden"
101
+ >
102
+ <AccordionItem value="item-1" className="border-b last:border-b-0">
103
+ <AccordionHeader className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
104
+ <span className="font-medium">Section One</span>
105
+ <AccordionTrigger />
106
+ </AccordionHeader>
107
+ <AccordionContent className="overflow-hidden">
108
+ <div className="p-4 pt-0">
109
+ Content for section one. Multiple sections can be open
110
+ simultaneously.
111
+ </div>
112
+ </AccordionContent>
113
+ </AccordionItem>
114
+
115
+ <AccordionItem value="item-2" className="border-b last:border-b-0">
116
+ <AccordionHeader className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
117
+ <span className="font-medium">Section Two</span>
118
+ <AccordionTrigger />
119
+ </AccordionHeader>
120
+ <AccordionContent className="overflow-hidden">
121
+ <div className="p-4 pt-0">
122
+ Content for section two. Try opening multiple sections at once.
123
+ </div>
124
+ </AccordionContent>
125
+ </AccordionItem>
126
+
127
+ <AccordionItem value="item-3" className="border-b last:border-b-0">
128
+ <AccordionHeader className="flex items-center justify-between p-4 cursor-pointer hover:bg-gray-50">
129
+ <span className="font-medium">Section Three</span>
130
+ <AccordionTrigger />
131
+ </AccordionHeader>
132
+ <AccordionContent className="overflow-hidden">
133
+ <div className="p-4 pt-0">
134
+ Content for section three. All three can be expanded together.
135
+ </div>
136
+ </AccordionContent>
137
+ </AccordionItem>
138
+ </Accordion>
139
+ </div>
140
+ );
141
+ },
142
+ };
143
+
144
+ // IWBI Standard Table styled variant
145
+ export const IWBIConceptsTable: Story = {
146
+ render: () => {
147
+ const [expandedConcepts, setExpandedConcepts] = useState<Set<string>>(
148
+ new Set(),
149
+ );
150
+
151
+ const hasAnyExpanded = expandedConcepts.size > 0;
152
+
153
+ const toggleAllConcepts = () => {
154
+ if (hasAnyExpanded) {
155
+ setExpandedConcepts(new Set());
156
+ } else {
157
+ setExpandedConcepts(new Set(concepts.map((c) => c.id)));
158
+ }
159
+ };
160
+
161
+ const navigateTo = (path: string) => {
162
+ console.log(path);
163
+ };
164
+
165
+ function getSlugFromName(name: string) {
166
+ return name.toLowerCase().replace(/\s+/g, "-");
167
+ }
168
+
169
+ return (
170
+ <AccordionContainer className="standard-table-container">
171
+ <AccordionSectionHeader
172
+ title="CONCEPTS"
173
+ hasExpanded={hasAnyExpanded}
174
+ onToggleAll={toggleAllConcepts}
175
+ className="standard-table-header"
176
+ />
177
+
178
+ <Accordion
179
+ allowMultiple={true}
180
+ expandedValues={expandedConcepts}
181
+ onExpandedChange={setExpandedConcepts}
182
+ className="border border-blue-200 rounded-xl overflow-hidden"
183
+ >
184
+ {[...concepts].map((concept) => {
185
+ const slug = getSlugFromName(concept.name);
186
+ const Icon = CONCEPT_ICONS[slug];
187
+ const color = getConceptColor(slug);
188
+
189
+ return (
190
+ <AccordionItem
191
+ key={concept.id}
192
+ value={concept.id}
193
+ className="standard-table-row"
194
+ >
195
+ <AccordionHeader
196
+ onClick={() => navigateTo("/")}
197
+ className="standard-table-row-header"
198
+ >
199
+ <Icon active={true} className="size-12 shrink-0" />
200
+ <span className="overline-large flex-1">{concept.name}</span>
201
+ <AccordionTrigger className="standard-table-trigger" />
202
+ </AccordionHeader>
203
+
204
+ <AccordionContent className="standard-table-content">
205
+ <div className="standard-table-content__inner">
206
+ {concept.themes.map((theme) => (
207
+ <AccordionListRow
208
+ key={theme.id}
209
+ badge={
210
+ <Tag
211
+ variant="code"
212
+ style={{
213
+ backgroundColor: color.contrast || color.solid,
214
+ borderColor: color.border,
215
+ color: "white",
216
+ }}
217
+ >
218
+ {theme.code}
219
+ </Tag>
220
+ }
221
+ title={theme.name}
222
+ titleClassName="standard-table-list-row__title body-large"
223
+ className="standard-table-list-row standard-table-list-row--nested"
224
+ onClick={() => navigateTo("/")}
225
+ />
226
+ ))}
227
+ </div>
228
+ </AccordionContent>
229
+ </AccordionItem>
230
+ );
231
+ })}
232
+ </Accordion>
233
+ </AccordionContainer>
234
+ );
235
+ },
236
+ };
237
+
238
+ export const IWBIThemesTable: Story = {
239
+ render: () => {
240
+ const conceptSlug = "community";
241
+
242
+ const [expandedThemes, setExpandedThemes] = useState<Set<string>>(
243
+ new Set(),
244
+ );
245
+
246
+ const hasAnyExpanded = expandedThemes.size > 0;
247
+
248
+ const toggleAllThemes = () => {
249
+ if (hasAnyExpanded) {
250
+ setExpandedThemes(new Set());
251
+ } else {
252
+ setExpandedThemes(new Set(conceptData?.themes.map((t) => t.id) || []));
253
+ }
254
+ };
255
+
256
+ const navigateTo = (path: string) => {
257
+ console.log(path);
258
+ };
259
+
260
+ const conceptData = concepts.find(
261
+ (c) => c.name.toLowerCase() === conceptSlug.toLowerCase(),
262
+ );
263
+
264
+ const conceptColor = getConceptColor(conceptSlug);
265
+
266
+ return (
267
+ <AccordionContainer className="standard-table-container">
268
+ <AccordionSectionHeader
269
+ title="THEMES"
270
+ hasExpanded={hasAnyExpanded}
271
+ onToggleAll={toggleAllThemes}
272
+ className="standard-table-header"
273
+ />
274
+
275
+ <Accordion
276
+ allowMultiple={true}
277
+ expandedValues={expandedThemes}
278
+ onExpandedChange={setExpandedThemes}
279
+ className="border border-blue-200 rounded-xl overflow-hidden"
280
+ >
281
+ {conceptData?.themes.map((theme) => {
282
+ return (
283
+ <AccordionItem
284
+ key={theme.id}
285
+ value={theme.id}
286
+ className="standard-table-row"
287
+ >
288
+ <AccordionHeader
289
+ onClick={() => navigateTo("/")}
290
+ className="standard-table-row-header"
291
+ >
292
+ <Tag
293
+ variant="code"
294
+ style={{
295
+ backgroundColor:
296
+ conceptColor.contrast || conceptColor.solid,
297
+ borderColor: conceptColor.border,
298
+ color: "white",
299
+ }}
300
+ >
301
+ {theme.code}
302
+ </Tag>
303
+ <Text as="p" variant="body-large" className="flex-1">
304
+ {theme.name}
305
+ </Text>
306
+ <AccordionTrigger className="standard-table-trigger" />
307
+ </AccordionHeader>
308
+
309
+ <AccordionContent className="standard-table-content">
310
+ <div className="standard-table-content__inner">
311
+ {theme.strategies.map((strategy) => (
312
+ <AccordionListRow
313
+ key={strategy.id}
314
+ badge={
315
+ <Tag
316
+ variant="code"
317
+ style={{
318
+ backgroundColor: conceptColor.light,
319
+ borderColor: conceptColor.border,
320
+ color:
321
+ conceptColor.contrast || conceptColor.solid,
322
+ }}
323
+ >
324
+ {strategy.code}
325
+ </Tag>
326
+ }
327
+ title={strategy.name}
328
+ titleClassName="standard-table-list-row__title body-large"
329
+ className="standard-table-list-row standard-table-list-row--nested"
330
+ onClick={() => navigateTo("/")}
331
+ rightContent={
332
+ <span className="flex justify-center items-center number-large font-semibold">
333
+ {strategy.points}{" "}
334
+ <span className="overline-medium ml-1">
335
+ PT
336
+ <span
337
+ className={
338
+ strategy.points === "1"
339
+ ? "opacity-0"
340
+ : "opacity-100"
341
+ }
342
+ >
343
+ S
344
+ </span>
345
+ </span>
346
+ </span>
347
+ }
348
+ />
349
+ ))}
350
+ </div>
351
+ </AccordionContent>
352
+ </AccordionItem>
353
+ );
354
+ })}
355
+ </Accordion>
356
+ </AccordionContainer>
357
+ );
358
+ },
359
+ };
360
+
361
+ export const IWBIStrategiesTable: Story = {
362
+ render: () => {
363
+ const conceptSlug = "community";
364
+ const themeCode = "C8";
365
+
366
+ const navigateTo = (path: string) => {
367
+ console.log(path);
368
+ };
369
+
370
+ const conceptData = concepts.find(
371
+ (c) => c.name.toLowerCase() === conceptSlug.toLowerCase(),
372
+ );
373
+ const themeData = conceptData?.themes.find((t) => t.code === themeCode);
374
+
375
+ const conceptColor = getConceptColor(conceptSlug);
376
+
377
+ return (
378
+ <AccordionContainer className="standard-table-container">
379
+ <AccordionSectionHeader
380
+ title="STRATEGIES"
381
+ className="standard-table-header"
382
+ />
383
+
384
+ <Accordion className="border border-blue-200 rounded-xl overflow-hidden">
385
+ {themeData?.strategies.map((strategy) => {
386
+ return (
387
+ <AccordionItem
388
+ key={strategy.id}
389
+ value={strategy.id}
390
+ className="standard-table-row"
391
+ >
392
+ <AccordionHeader
393
+ onClick={() => navigateTo("/")}
394
+ className="standard-table-row-header"
395
+ >
396
+ <Tag
397
+ variant="code"
398
+ style={{
399
+ backgroundColor: conceptColor.light,
400
+ borderColor: conceptColor.border,
401
+ color: conceptColor.contrast || conceptColor.solid,
402
+ }}
403
+ >
404
+ {strategy.code}
405
+ </Tag>
406
+ <Text as="p" variant="body-large" className="flex-1">
407
+ {strategy.name}
408
+ </Text>
409
+ <span className="flex justify-center items-center number-large font-semibold">
410
+ {strategy.points}{" "}
411
+ <span className="overline-medium ml-1">
412
+ PT
413
+ <span
414
+ className={
415
+ strategy.points === "1" ? "opacity-0" : "opacity-100"
416
+ }
417
+ >
418
+ S
419
+ </span>
420
+ </span>
421
+ </span>
422
+ </AccordionHeader>
423
+ </AccordionItem>
424
+ );
425
+ })}
426
+ </Accordion>
427
+ </AccordionContainer>
428
+ );
429
+ },
430
+ };
@@ -8,7 +8,7 @@ import {
8
8
  PageHeaderDescription,
9
9
  } from "../components/ui/page-header";
10
10
  import { BadgeCertificationBronze } from "../components/icons/AchievementBadges";
11
- import { CodeBadge } from "../components/ui/code-badge";
11
+ import { Tag } from "../components/ui/tag";
12
12
  import {
13
13
  Tooltip,
14
14
  TooltipTrigger,
@@ -96,14 +96,16 @@ export const ThemeHeader: Story = {
96
96
  <PageHeader>
97
97
  <PageHeaderTopSection className="mb-4">
98
98
  <PageHeaderLeftContent>
99
- <CodeBadge
100
- code={themeCode}
99
+ <Tag
100
+ variant="code"
101
101
  style={{
102
102
  backgroundColor: conceptColor.contrast || conceptColor.solid,
103
103
  borderColor: conceptColor.border,
104
104
  color: "white",
105
105
  }}
106
- />
106
+ >
107
+ {themeCode}
108
+ </Tag>
107
109
  <Tooltip trigger="click">
108
110
  <TooltipTrigger>
109
111
  <span className="body-base text-gray-600 underline-dotted cursor-pointer">
@@ -2,7 +2,6 @@ import { LayoutGroup } from "motion/react";
2
2
  import { motion } from "motion/react";
3
3
  import { AnimatePresence } from "motion/react";
4
4
  import { getConceptColor } from "../lib/concept-colors";
5
- import { CodeBadge } from "../components/ui/code-badge";
6
5
  import { capitalize, cn } from "../lib/utils";
7
6
  import { Tag } from "../components/ui/tag";
8
7
  import { CONCEPT_ICONS } from "../lib/concept-icons";
@@ -187,8 +186,8 @@ const ExploreSidePanelView = (args: any) => {
187
186
  onMouseLeave={() => setHoveredTheme(null)}
188
187
  className="cursor-pointer"
189
188
  >
190
- <CodeBadge
191
- code={theme.code}
189
+ <Tag
190
+ variant="code"
192
191
  className={cn(!isSelected && "hover:border-blue-300")}
193
192
  style={
194
193
  isFilled
@@ -220,7 +219,9 @@ const ExploreSidePanelView = (args: any) => {
220
219
  borderWidth: "3px",
221
220
  }
222
221
  }
223
- />
222
+ >
223
+ {theme.code}
224
+ </Tag>
224
225
  </button>
225
226
  );
226
227
  })}
@@ -281,8 +282,8 @@ const ExploreSidePanelView = (args: any) => {
281
282
  onMouseLeave={() => setHoveredStrategy(null)}
282
283
  className="cursor-pointer"
283
284
  >
284
- <CodeBadge
285
- code={strategy.code}
285
+ <Tag
286
+ variant="code"
286
287
  className={cn(
287
288
  !isSelected && "hover:border-blue-300",
288
289
  "border-[3px]",
@@ -304,7 +305,9 @@ const ExploreSidePanelView = (args: any) => {
304
305
  borderWidth: "3px",
305
306
  }
306
307
  }
307
- />
308
+ >
309
+ {strategy.code}
310
+ </Tag>
308
311
  </button>
309
312
  );
310
313
  })}
@@ -343,14 +346,14 @@ const ExploreSidePanelView = (args: any) => {
343
346
  <Label>PURSUIT</Label>
344
347
  <div className=" inline-flex gap-3">
345
348
  <Tag
346
- variant="light"
349
+ variant="text"
347
350
  active={activePursuits.has("certification")}
348
351
  onClick={() => onPursuitToggle("certification")}
349
352
  >
350
353
  Certification
351
354
  </Tag>
352
355
  <Tag
353
- variant="light"
356
+ variant="text"
354
357
  active={activePursuits.has("rating")}
355
358
  onClick={() => onPursuitToggle("rating")}
356
359
  >