@syscore/ui-library 1.10.0 → 1.11.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,38 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface UtilityMessageProps {
5
+ className?: string;
6
+ }
7
+
8
+ export const UtilityMessage: React.FC<UtilityMessageProps> = ({
9
+ className,
10
+ }) => {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "size-4 flex items-center justify-center text-gray-500",
15
+ className,
16
+ )}
17
+ >
18
+ <svg
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ width="22"
21
+ height="21"
22
+ viewBox="0 0 22 21"
23
+ fill="none"
24
+ className={cn("size-[14px] text-inherit")}
25
+ >
26
+ <path
27
+ d="M21 15C21 15.5304 20.7893 16.0391 20.4142 16.4142C20.0391 16.7893 19.5304 17 19 17H5.828C5.29761 17.0001 4.78899 17.2109 4.414 17.586L2.212 19.788C2.1127 19.8873 1.9862 19.9549 1.84849 19.9823C1.71077 20.0097 1.56803 19.9956 1.43831 19.9419C1.30858 19.8881 1.1977 19.7971 1.11969 19.6804C1.04167 19.5637 1.00002 19.4264 1 19.286V3C1 2.46957 1.21071 1.96086 1.58579 1.58579C1.96086 1.21071 2.46957 1 3 1H19C19.5304 1 20.0391 1.21071 20.4142 1.58579C20.7893 1.96086 21 2.46957 21 3V15Z"
28
+ fill="currentColor"
29
+ fill-opacity="0.2"
30
+ stroke="currentColor"
31
+ stroke-width="2"
32
+ stroke-linecap="round"
33
+ stroke-linejoin="round"
34
+ />
35
+ </svg>
36
+ </div>
37
+ );
38
+ };
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface UtilityTrashProps {
5
+ className?: string;
6
+ }
7
+
8
+ export const UtilityTrash: React.FC<UtilityTrashProps> = ({ className }) => {
9
+ return (
10
+ <div
11
+ className={cn(
12
+ "size-4 flex items-center justify-center text-gray-500",
13
+ className,
14
+ )}
15
+ >
16
+ <svg
17
+ width="15"
18
+ height="16"
19
+ viewBox="0 0 15 16"
20
+ fill="none"
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ className={cn("size-[14px] text-inherit")}
23
+ >
24
+ <path
25
+ d="M11.9504 3.5498V13.3498C11.9504 13.7211 11.8029 14.0772 11.5403 14.3398C11.2778 14.6023 10.9217 14.7498 10.5504 14.7498H3.55039C3.17909 14.7498 2.82299 14.6023 2.56044 14.3398C2.29789 14.0772 2.15039 13.7211 2.15039 13.3498V3.5498"
26
+ fill="white"
27
+ />
28
+ <path
29
+ d="M11.9504 3.5498V13.3498C11.9504 13.7211 11.8029 14.0772 11.5403 14.3398C11.2778 14.6023 10.9217 14.7498 10.5504 14.7498H3.55039C3.17909 14.7498 2.82299 14.6023 2.56044 14.3398C2.29789 14.0772 2.15039 13.7211 2.15039 13.3498V3.5498"
30
+ stroke="currentColor"
31
+ stroke-width="1.5"
32
+ stroke-linecap="round"
33
+ stroke-linejoin="round"
34
+ />
35
+ <path
36
+ d="M0.75 3.5498H13.35"
37
+ stroke="currentColor"
38
+ stroke-width="1.5"
39
+ stroke-linecap="round"
40
+ stroke-linejoin="round"
41
+ />
42
+ <path
43
+ d="M4.25 3.55V2.15C4.25 1.7787 4.3975 1.4226 4.66005 1.16005C4.9226 0.8975 5.2787 0.75 5.65 0.75H8.45C8.8213 0.75 9.1774 0.8975 9.43995 1.16005C9.7025 1.4226 9.85 1.7787 9.85 2.15V3.55"
44
+ stroke="currentColor"
45
+ stroke-width="1.5"
46
+ stroke-linecap="round"
47
+ stroke-linejoin="round"
48
+ />
49
+ </svg>
50
+ </div>
51
+ );
52
+ };
@@ -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 ref={ref} className={cn(className)} {...props}>
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 ref={ref} onClick={onClick} className={cn(className)} {...props}>
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={{ willChange: "opacity" }}
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"
@@ -20,6 +20,7 @@ import { UtilityClassification } from "../components/icons/UtilityClassification
20
20
  import { UtilityClose } from "../components/icons/UtilityClose";
21
21
  import { UtilityDrag } from "../components/icons/UtilityDrag";
22
22
  import { UtilityEdit } from "../components/icons/UtilityEdit";
23
+ import { UtilityMessage } from "../components/icons/UtilityMessage";
23
24
  import { UtilityOptions } from "../components/icons/UtilityOptions";
24
25
  import { UtilityPortfolio } from "../components/icons/UtilityPortfolio";
25
26
  import { UtilityReset } from "../components/icons/UtilityReset";
@@ -27,6 +28,7 @@ import { UtilityScoring } from "../components/icons/UtilityScoring";
27
28
  import { UtilitySearch } from "../components/icons/UtilitySearch";
28
29
  import { UtilitySort } from "../components/icons/UtilitySort";
29
30
  import { UtilityText } from "../components/icons/UtilityText";
31
+ import { UtilityTriangleInfo } from "../components/icons/UtilityTriangleInfo";
30
32
  import { SealWell } from "../components/icons/SealWell";
31
33
  import { SealIwbiMember } from "../components/icons/SealIwbiMember";
32
34
  import { SealWellCertification } from "../components/icons/SealWellCertification";
@@ -49,6 +51,7 @@ import {
49
51
  SealProductProvider,
50
52
  SealProviders,
51
53
  } from "../components/icons/ProviderSeals";
54
+ import { UtilityTrash } from "@/components/icons/UtilityTrash";
52
55
 
53
56
  const meta = {
54
57
  title: "Review/Icons",
@@ -232,6 +235,10 @@ export const UtilityIcons: Story = {
232
235
  <UtilityEdit />
233
236
  <span className="text-sm font-medium">Edit</span>
234
237
  </div>
238
+ <div className="flex flex-col items-center gap-2">
239
+ <UtilityMessage />
240
+ <span className="text-sm font-medium">Message</span>
241
+ </div>
235
242
  <div className="flex flex-col items-center gap-2">
236
243
  <UtilityOptions />
237
244
  <span className="text-sm font-medium">Options</span>
@@ -260,6 +267,14 @@ export const UtilityIcons: Story = {
260
267
  <UtilityText />
261
268
  <span className="text-sm font-medium">Text</span>
262
269
  </div>
270
+ <div className="flex flex-col items-center gap-2">
271
+ <UtilityTrash />
272
+ <span className="text-sm font-medium">Trash</span>
273
+ </div>
274
+ <div className="flex flex-col items-center gap-2">
275
+ <UtilityTriangleInfo />
276
+ <span className="text-sm font-medium">TriangleInfo</span>
277
+ </div>
263
278
  </div>
264
279
  </div>
265
280
  </div>