@syscore/ui-library 1.10.0 → 1.10.1
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/ui/accordion.tsx +39 -9
- package/client/ui/Accordion.stories.tsx +187 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +31 -8
- package/package.json +1 -1
|
@@ -33,6 +33,8 @@ interface AccordionItemContextValue {
|
|
|
33
33
|
value: string;
|
|
34
34
|
/** Whether this item is expanded */
|
|
35
35
|
isExpanded: boolean;
|
|
36
|
+
/** Whether to remove borders when open */
|
|
37
|
+
noBorderOnOpen?: boolean;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const AccordionItemContext =
|
|
@@ -324,21 +326,32 @@ AccordionHeaderRow.displayName = "AccordionHeaderRow";
|
|
|
324
326
|
interface AccordionItemProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
325
327
|
/** Unique value for this item */
|
|
326
328
|
value: string;
|
|
329
|
+
/** Remove bottom border when the accordion item is open */
|
|
330
|
+
noBorderOnOpen?: boolean;
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
const AccordionItem = React.forwardRef<HTMLDivElement, AccordionItemProps>(
|
|
330
|
-
({ value, children, className, ...props }, ref) => {
|
|
334
|
+
({ value, children, className, noBorderOnOpen, ...props }, ref) => {
|
|
331
335
|
const { isExpanded: isExpandedFn } = useAccordion();
|
|
332
336
|
const isExpanded = isExpandedFn(value);
|
|
333
337
|
|
|
334
338
|
const itemContextValue = React.useMemo(
|
|
335
|
-
() => ({ value, isExpanded }),
|
|
336
|
-
[value, isExpanded],
|
|
339
|
+
() => ({ value, isExpanded, noBorderOnOpen }),
|
|
340
|
+
[value, isExpanded, noBorderOnOpen],
|
|
337
341
|
);
|
|
338
342
|
|
|
339
343
|
return (
|
|
340
344
|
<AccordionItemContext.Provider value={itemContextValue}>
|
|
341
|
-
<div
|
|
345
|
+
<div
|
|
346
|
+
ref={ref}
|
|
347
|
+
className={cn(className)}
|
|
348
|
+
style={
|
|
349
|
+
noBorderOnOpen && isExpanded
|
|
350
|
+
? { borderBottom: "none" }
|
|
351
|
+
: undefined
|
|
352
|
+
}
|
|
353
|
+
{...props}
|
|
354
|
+
>
|
|
342
355
|
{children}
|
|
343
356
|
</div>
|
|
344
357
|
</AccordionItemContext.Provider>
|
|
@@ -349,12 +362,26 @@ const AccordionItem = React.forwardRef<HTMLDivElement, AccordionItemProps>(
|
|
|
349
362
|
AccordionItem.displayName = "AccordionItem";
|
|
350
363
|
|
|
351
364
|
// AccordionHeader
|
|
352
|
-
type AccordionHeaderProps = React.ComponentPropsWithoutRef<"div"
|
|
365
|
+
type AccordionHeaderProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
366
|
+
/** Remove background color when the accordion item is open */
|
|
367
|
+
transparentOnOpen?: boolean;
|
|
368
|
+
};
|
|
353
369
|
|
|
354
370
|
const AccordionHeader = React.forwardRef<HTMLDivElement, AccordionHeaderProps>(
|
|
355
|
-
({ children, className, onClick, ...props }, ref) => {
|
|
371
|
+
({ children, className, onClick, transparentOnOpen, ...props }, ref) => {
|
|
372
|
+
const { isExpanded } = useAccordionItem();
|
|
356
373
|
return (
|
|
357
|
-
<div
|
|
374
|
+
<div
|
|
375
|
+
ref={ref}
|
|
376
|
+
onClick={onClick}
|
|
377
|
+
className={cn(className)}
|
|
378
|
+
style={
|
|
379
|
+
transparentOnOpen && isExpanded
|
|
380
|
+
? { backgroundColor: "transparent" }
|
|
381
|
+
: undefined
|
|
382
|
+
}
|
|
383
|
+
{...props}
|
|
384
|
+
>
|
|
358
385
|
{children}
|
|
359
386
|
</div>
|
|
360
387
|
);
|
|
@@ -409,7 +436,7 @@ type AccordionContentProps = React.ComponentPropsWithoutRef<"div">;
|
|
|
409
436
|
|
|
410
437
|
const AccordionContent = React.forwardRef<HTMLDivElement, AccordionContentProps>(
|
|
411
438
|
({ children, className, ...props }, ref) => {
|
|
412
|
-
const { isExpanded } = useAccordionItem();
|
|
439
|
+
const { isExpanded, noBorderOnOpen } = useAccordionItem();
|
|
413
440
|
|
|
414
441
|
return (
|
|
415
442
|
<AnimatePresence initial={false}>
|
|
@@ -420,7 +447,10 @@ const AccordionContent = React.forwardRef<HTMLDivElement, AccordionContentProps>
|
|
|
420
447
|
exit={{ height: 0, opacity: 0 }}
|
|
421
448
|
transition={{ duration: 0.3, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
422
449
|
className={cn(className)}
|
|
423
|
-
style={{
|
|
450
|
+
style={{
|
|
451
|
+
willChange: "opacity",
|
|
452
|
+
...(noBorderOnOpen ? { borderTop: "none" } : {}),
|
|
453
|
+
}}
|
|
424
454
|
>
|
|
425
455
|
<div ref={ref} {...props}>
|
|
426
456
|
{children}
|
|
@@ -195,6 +195,7 @@ export const IWBIConceptsTable: Story = {
|
|
|
195
195
|
<AccordionHeader
|
|
196
196
|
onClick={() => navigateTo("/")}
|
|
197
197
|
className="standard-table-row-header"
|
|
198
|
+
transparentOnOpen
|
|
198
199
|
>
|
|
199
200
|
<Icon active={true} className="size-12 shrink-0" />
|
|
200
201
|
<span className="overline-large flex-1">{concept.name}</span>
|
|
@@ -235,6 +236,190 @@ export const IWBIConceptsTable: Story = {
|
|
|
235
236
|
},
|
|
236
237
|
};
|
|
237
238
|
|
|
239
|
+
// Showcases transparentOnOpen and noBorderOnOpen props
|
|
240
|
+
export const TransparentOnOpen: Story = {
|
|
241
|
+
render: () => {
|
|
242
|
+
const [expandedConcepts, setExpandedConcepts] = useState<Set<string>>(
|
|
243
|
+
new Set(),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const hasAnyExpanded = expandedConcepts.size > 0;
|
|
247
|
+
|
|
248
|
+
const toggleAllConcepts = () => {
|
|
249
|
+
if (hasAnyExpanded) {
|
|
250
|
+
setExpandedConcepts(new Set());
|
|
251
|
+
} else {
|
|
252
|
+
setExpandedConcepts(new Set(concepts.map((c) => c.id)));
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const navigateTo = (path: string) => {
|
|
257
|
+
console.log(path);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
function getSlugFromName(name: string) {
|
|
261
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div className="grid grid-cols-2 gap-4">
|
|
266
|
+
<div className="flex flex-col gap-2">
|
|
267
|
+
<div className="flex gap-2">
|
|
268
|
+
<code className="text-xs bg-gray-100 px-2 py-1 rounded">
|
|
269
|
+
AccordionItem: noBorderOnOpen
|
|
270
|
+
</code>
|
|
271
|
+
<code className="text-xs bg-gray-100 px-2 py-1 rounded">
|
|
272
|
+
AccordionHeader: transparentOnOpen
|
|
273
|
+
</code>
|
|
274
|
+
</div>
|
|
275
|
+
<AccordionContainer className="standard-table-container">
|
|
276
|
+
<AccordionSectionHeader
|
|
277
|
+
title="no border and transparent on open"
|
|
278
|
+
hasExpanded={hasAnyExpanded}
|
|
279
|
+
onToggleAll={toggleAllConcepts}
|
|
280
|
+
className="standard-table-header"
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
<Accordion
|
|
284
|
+
allowMultiple={true}
|
|
285
|
+
expandedValues={expandedConcepts}
|
|
286
|
+
onExpandedChange={setExpandedConcepts}
|
|
287
|
+
className="border border-blue-200 rounded-xl overflow-hidden"
|
|
288
|
+
>
|
|
289
|
+
{[...concepts].map((concept) => {
|
|
290
|
+
const slug = getSlugFromName(concept.name);
|
|
291
|
+
const Icon = CONCEPT_ICONS[slug];
|
|
292
|
+
const color = getConceptColor(slug);
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<AccordionItem
|
|
296
|
+
key={concept.id}
|
|
297
|
+
value={concept.id}
|
|
298
|
+
className="standard-table-row"
|
|
299
|
+
noBorderOnOpen
|
|
300
|
+
>
|
|
301
|
+
<AccordionHeader
|
|
302
|
+
onClick={() => navigateTo("/")}
|
|
303
|
+
className="standard-table-row-header"
|
|
304
|
+
transparentOnOpen
|
|
305
|
+
>
|
|
306
|
+
<Icon active={true} className="size-12 shrink-0" />
|
|
307
|
+
<span className="overline-large flex-1">
|
|
308
|
+
{concept.name}
|
|
309
|
+
</span>
|
|
310
|
+
<AccordionTrigger className="standard-table-trigger" />
|
|
311
|
+
</AccordionHeader>
|
|
312
|
+
|
|
313
|
+
<AccordionContent className="standard-table-content">
|
|
314
|
+
<div className="standard-table-content__inner">
|
|
315
|
+
{concept.themes.slice(0, 1).map((theme) => (
|
|
316
|
+
<AccordionListRow
|
|
317
|
+
key={theme.id}
|
|
318
|
+
badge={
|
|
319
|
+
<Tag
|
|
320
|
+
variant="code"
|
|
321
|
+
style={{
|
|
322
|
+
backgroundColor: color.contrast || color.solid,
|
|
323
|
+
borderColor: color.border,
|
|
324
|
+
color: "white",
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
{theme.code}
|
|
328
|
+
</Tag>
|
|
329
|
+
}
|
|
330
|
+
title={theme.name}
|
|
331
|
+
titleClassName="standard-table-list-row__title body-large"
|
|
332
|
+
className="standard-table-list-row standard-table-list-row--nested"
|
|
333
|
+
onClick={() => navigateTo("/")}
|
|
334
|
+
/>
|
|
335
|
+
))}
|
|
336
|
+
</div>
|
|
337
|
+
</AccordionContent>
|
|
338
|
+
</AccordionItem>
|
|
339
|
+
);
|
|
340
|
+
})}
|
|
341
|
+
</Accordion>
|
|
342
|
+
</AccordionContainer>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div className="flex flex-col gap-2">
|
|
346
|
+
<div className="flex gap-2">
|
|
347
|
+
<code className="text-xs bg-gray-100 px-2 py-1 rounded">
|
|
348
|
+
No props
|
|
349
|
+
</code>
|
|
350
|
+
</div>
|
|
351
|
+
<AccordionContainer className="standard-table-container">
|
|
352
|
+
<AccordionSectionHeader
|
|
353
|
+
title="Default"
|
|
354
|
+
hasExpanded={hasAnyExpanded}
|
|
355
|
+
onToggleAll={toggleAllConcepts}
|
|
356
|
+
className="standard-table-header"
|
|
357
|
+
/>
|
|
358
|
+
|
|
359
|
+
<Accordion
|
|
360
|
+
allowMultiple={true}
|
|
361
|
+
expandedValues={expandedConcepts}
|
|
362
|
+
onExpandedChange={setExpandedConcepts}
|
|
363
|
+
className="border border-blue-200 rounded-xl overflow-hidden"
|
|
364
|
+
>
|
|
365
|
+
{[...concepts].map((concept) => {
|
|
366
|
+
const slug = getSlugFromName(concept.name);
|
|
367
|
+
const Icon = CONCEPT_ICONS[slug];
|
|
368
|
+
const color = getConceptColor(slug);
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<AccordionItem
|
|
372
|
+
key={concept.id}
|
|
373
|
+
value={concept.id}
|
|
374
|
+
className="standard-table-row"
|
|
375
|
+
>
|
|
376
|
+
<AccordionHeader
|
|
377
|
+
onClick={() => navigateTo("/")}
|
|
378
|
+
className="standard-table-row-header"
|
|
379
|
+
>
|
|
380
|
+
<Icon active={true} className="size-12 shrink-0" />
|
|
381
|
+
<span className="overline-large flex-1">
|
|
382
|
+
{concept.name}
|
|
383
|
+
</span>
|
|
384
|
+
<AccordionTrigger className="standard-table-trigger" />
|
|
385
|
+
</AccordionHeader>
|
|
386
|
+
|
|
387
|
+
<AccordionContent className="standard-table-content">
|
|
388
|
+
<div className="standard-table-content__inner">
|
|
389
|
+
{concept.themes.slice(0, 1).map((theme) => (
|
|
390
|
+
<AccordionListRow
|
|
391
|
+
key={theme.id}
|
|
392
|
+
badge={
|
|
393
|
+
<Tag
|
|
394
|
+
variant="code"
|
|
395
|
+
style={{
|
|
396
|
+
backgroundColor: color.contrast || color.solid,
|
|
397
|
+
borderColor: color.border,
|
|
398
|
+
color: "white",
|
|
399
|
+
}}
|
|
400
|
+
>
|
|
401
|
+
{theme.code}
|
|
402
|
+
</Tag>
|
|
403
|
+
}
|
|
404
|
+
title={theme.name}
|
|
405
|
+
titleClassName="standard-table-list-row__title body-large"
|
|
406
|
+
className="standard-table-list-row standard-table-list-row--nested"
|
|
407
|
+
onClick={() => navigateTo("/")}
|
|
408
|
+
/>
|
|
409
|
+
))}
|
|
410
|
+
</div>
|
|
411
|
+
</AccordionContent>
|
|
412
|
+
</AccordionItem>
|
|
413
|
+
);
|
|
414
|
+
})}
|
|
415
|
+
</Accordion>
|
|
416
|
+
</AccordionContainer>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
238
423
|
export const IWBIThemesTable: Story = {
|
|
239
424
|
render: () => {
|
|
240
425
|
const conceptSlug = "community";
|
|
@@ -288,6 +473,7 @@ export const IWBIThemesTable: Story = {
|
|
|
288
473
|
<AccordionHeader
|
|
289
474
|
onClick={() => navigateTo("/")}
|
|
290
475
|
className="standard-table-row-header"
|
|
476
|
+
transparentOnOpen
|
|
291
477
|
>
|
|
292
478
|
<Tag
|
|
293
479
|
variant="code"
|
|
@@ -392,6 +578,7 @@ export const IWBIStrategiesTable: Story = {
|
|
|
392
578
|
<AccordionHeader
|
|
393
579
|
onClick={() => navigateTo("/")}
|
|
394
580
|
className="standard-table-row-header"
|
|
581
|
+
transparentOnOpen
|
|
395
582
|
>
|
|
396
583
|
<Tag
|
|
397
584
|
variant="code"
|