@bccampus/ui-components 0.1.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.
Files changed (68) hide show
  1. package/README.md +69 -0
  2. package/components.json +22 -0
  3. package/dist/@bccampus-ui-components-0.1.0.tgz +0 -0
  4. package/dist/button.d.ts +16 -0
  5. package/dist/button.js +36 -0
  6. package/dist/caption.d.ts +13 -0
  7. package/dist/caption.js +27 -0
  8. package/dist/card.d.ts +40 -0
  9. package/dist/card.js +130 -0
  10. package/dist/horizontal-list.d.ts +9 -0
  11. package/dist/horizontal-list.js +145 -0
  12. package/dist/icon-generator.d.ts +50 -0
  13. package/dist/icon-generator.js +270 -0
  14. package/dist/index-DcqAdr0d.js +102 -0
  15. package/dist/jsx-runtime-BzflLqGi.js +282 -0
  16. package/dist/masked-image-generator.d.ts +57 -0
  17. package/dist/masked-image-generator.js +29 -0
  18. package/dist/mockServiceWorker.js +348 -0
  19. package/dist/tag.d.ts +16 -0
  20. package/dist/tag.js +32 -0
  21. package/dist/ui-components.d.ts +132 -0
  22. package/dist/ui-components.js +29 -0
  23. package/dist/utils-CRiPKpXj.js +2743 -0
  24. package/dist/utils.d.ts +5 -0
  25. package/dist/utils.js +4 -0
  26. package/eslint.config.js +31 -0
  27. package/index.html +13 -0
  28. package/package.json +85 -0
  29. package/public/mockServiceWorker.js +348 -0
  30. package/src/App.tsx +14 -0
  31. package/src/assets/icons/icon_01.svg +6 -0
  32. package/src/assets/icons/icon_02.svg +6 -0
  33. package/src/assets/icons/icon_03.svg +6 -0
  34. package/src/assets/icons/icon_04.svg +6 -0
  35. package/src/assets/icons/icon_05.svg +4 -0
  36. package/src/assets/icons/icon_06.svg +4 -0
  37. package/src/assets/images/bg_pattern_01.png +0 -0
  38. package/src/assets/images/bg_pattern_02.png +0 -0
  39. package/src/assets/images/bg_pattern_03.png +0 -0
  40. package/src/assets/images/bg_pattern_04.png +0 -0
  41. package/src/assets/images/image_01.jpg +0 -0
  42. package/src/assets/images/image_02.jpg +0 -0
  43. package/src/assets/images/image_03.webp +0 -0
  44. package/src/assets/images/image_04.png +0 -0
  45. package/src/assets/images/image_05.jpg +0 -0
  46. package/src/components/ui/button.tsx +47 -0
  47. package/src/components/ui/card.tsx +147 -0
  48. package/src/components/ui/horizontal-list.tsx +50 -0
  49. package/src/components/ui/icon-generator/generate-tiles.tsx +243 -0
  50. package/src/components/ui/icon-generator/icon-generator.tsx +51 -0
  51. package/src/components/ui/icon-generator/masked-image-generator.tsx +38 -0
  52. package/src/components/ui/icon-generator/types.ts +53 -0
  53. package/src/components/ui/index.ts +7 -0
  54. package/src/components/ui/tag.tsx +39 -0
  55. package/src/components/ui/typography/caption.tsx +32 -0
  56. package/src/lib/utils.ts +6 -0
  57. package/src/main.tsx +12 -0
  58. package/src/styles/all.css +4 -0
  59. package/src/styles/colors.css +106 -0
  60. package/src/styles/fonts.css +9 -0
  61. package/src/styles/index.css +7 -0
  62. package/src/styles/theme.css +126 -0
  63. package/src/styles/typography.css +479 -0
  64. package/src/vite-env.d.ts +1 -0
  65. package/tsconfig.app.json +38 -0
  66. package/tsconfig.json +19 -0
  67. package/tsconfig.node.json +25 -0
  68. package/vite.config.ts +44 -0
@@ -0,0 +1,147 @@
1
+ import { cn } from "@/lib/utils";
2
+ import { Caption } from "./typography/caption";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ const cardVariants = cva("group @container/card flex flex-col rounded-2xl gap-3", {
6
+ variants: {
7
+ variant: {
8
+ default: "bg-card text-card-foreground",
9
+ dark: "bg-card-foreground text-card",
10
+ },
11
+ noBorder: {
12
+ true: "",
13
+ false: "border border-complement-1-100 dark:border-complement-1-900",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ noBorder: false,
19
+ },
20
+ });
21
+
22
+ type CardProps<T extends boolean> = (T extends true ? React.ComponentProps<"a"> : React.ComponentProps<"div">) &
23
+ VariantProps<typeof cardVariants> & {
24
+ asLink?: T;
25
+ };
26
+
27
+ function Card<T extends boolean = false>({ className, asLink, variant, noBorder, ...props }: CardProps<T>) {
28
+ const Comp = asLink ? "a" : "div";
29
+
30
+ const linkClassName = asLink ? "transition-all hover:bg-complement-1-50 dark:hover:border-complement-1-950" : "";
31
+
32
+ return (
33
+ //@ts-expect-error: props type will be correct
34
+ <Comp data-slot="card" className={cn(cardVariants({ variant, noBorder }), linkClassName, className)} {...props} />
35
+ );
36
+ }
37
+
38
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
39
+ return (
40
+ <div
41
+ data-slot="card-header"
42
+ className={cn("flex flex-col items-start gap-3 px-6 first:pt-6 last:pb-6", className)}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ function CardBody({ className, ...props }: React.ComponentProps<"div">) {
49
+ return (
50
+ <div
51
+ data-slot="card-body"
52
+ className={cn("flex flex-col items-start gap-3 px-6 first:pt-6 last:pb-6", className)}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ type CardTitleProps = React.ComponentProps<"div"> & { size?: "sm" | "md" | "lg" };
59
+ function CardTitle({ size = "md", className, ...props }: CardTitleProps) {
60
+ return (
61
+ <div
62
+ data-slot="card-title"
63
+ className={cn(
64
+ {
65
+ "heading-1": size === "lg",
66
+ "heading-2": size === "md",
67
+ "heading-3": size === "sm",
68
+ },
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ );
74
+ }
75
+ function CardSubtitle({ size = "md", className, ...props }: CardTitleProps) {
76
+ return (
77
+ <div
78
+ data-slot="card-title"
79
+ className={cn(
80
+ "text-secondary",
81
+ {
82
+ "heading-1": size === "lg",
83
+ "heading-2": size === "md",
84
+ "heading-3": size === "sm",
85
+ },
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ );
91
+ }
92
+ function CardCaption(props: React.ComponentProps<"div">) {
93
+ return <Caption data-slot="card-caption" {...props} />;
94
+ }
95
+
96
+ function CardMeta(props: React.ComponentProps<"div">) {
97
+ return <Caption data-slot="card-meta" variant="light" {...props} />;
98
+ }
99
+
100
+ function CardContent(props: React.ComponentProps<"div">) {
101
+ return <div data-slot="card-content" {...props} />;
102
+ }
103
+
104
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
105
+ return (
106
+ <div
107
+ data-slot="card-footer"
108
+ className={cn("flex flex-wrap items-center gap-2 px-6 pt-3 last:pb-6", className)}
109
+ {...props}
110
+ />
111
+ );
112
+ }
113
+
114
+ function CardMedia({ className, ...props }: React.ComponentProps<"div">) {
115
+ return (
116
+ <div
117
+ data-slot="card-media"
118
+ className={cn(
119
+ "relative flex-1 p-6 first:pb-3 last:pt-3 group-[.flex-row]:first:pr-0 group-[.flex-row]:first:pb-6 group-[.flex-row]:last:pl-0 group-[.flex-row]:last:pt-6",
120
+ className
121
+ )}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ function CardImage({ className, ...props }: React.ComponentProps<"img">) {
128
+ return (
129
+ <CardMedia>
130
+ <img className={cn("w-full h-full rounded-lg aspect-9/5 object-cover object-top", className)} {...props} />
131
+ </CardMedia>
132
+ );
133
+ }
134
+
135
+ export {
136
+ Card,
137
+ CardHeader,
138
+ CardBody,
139
+ CardFooter,
140
+ CardTitle,
141
+ CardSubtitle,
142
+ CardCaption,
143
+ CardMeta,
144
+ CardContent,
145
+ CardMedia,
146
+ CardImage,
147
+ };
@@ -0,0 +1,50 @@
1
+ import { cn } from "@/lib/utils";
2
+ import { Button } from "./button";
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { useCallback, useRef } from "react";
5
+
6
+ interface HorizontalListProps extends React.ComponentProps<"div"> {
7
+ toolbarLocation?: "bottom" | "top";
8
+ }
9
+
10
+ function HorizontalList({ className, children, toolbarLocation = "bottom", ...props }: HorizontalListProps) {
11
+ const containerRef = useRef<HTMLDivElement>(null);
12
+
13
+ const scrollLeft = useCallback(() => {
14
+ containerRef.current?.scrollBy({ left: -320 });
15
+ }, []);
16
+
17
+ const scrollRight = useCallback(() => {
18
+ containerRef.current?.scrollBy({ left: 320 });
19
+ }, []);
20
+
21
+ return (
22
+ <div
23
+ className={cn("flex gap-4", {
24
+ "flex-col": toolbarLocation === "bottom",
25
+ "flex-col-reverse": toolbarLocation === "top",
26
+ })}
27
+ >
28
+ <div
29
+ ref={containerRef}
30
+ className={cn(
31
+ "scrollbar-hidden overscroll-contain -ms-4 py-1 px-4 gap-4 sm:-ms-8 sm:px-8 sm:gap-8 flex flex-row flex-nowrap overflow-x-auto w-screen snap-x snap-mandatory touch-pan-x scroll-smooth",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ </div>
38
+ <div className="flex justify-center sm:justify-start">
39
+ <Button size="icon" variant="ghost" className="rounded-full" onClick={scrollLeft}>
40
+ <ChevronLeft className="size-9" />
41
+ </Button>
42
+ <Button size="icon" variant="ghost" className="rounded-full" onClick={scrollRight}>
43
+ <ChevronRight className="size-9" />
44
+ </Button>
45
+ </div>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export { HorizontalList };
@@ -0,0 +1,243 @@
1
+ import type { JSX } from "react";
2
+ import type { TileConfig } from "./types";
3
+ import { cn } from "@/lib/utils";
4
+ import { TileShape } from "./types";
5
+
6
+ const randomSet = [
7
+ TileShape.Square,
8
+ TileShape.Circle,
9
+ TileShape.Diamond,
10
+ TileShape.Hexagon,
11
+ TileShape.TriangleSE,
12
+ TileShape.TriangleSW,
13
+ TileShape.TriangleNW,
14
+ TileShape.TriangleNE,
15
+ TileShape.PieSE,
16
+ TileShape.PieSW,
17
+ TileShape.PieNW,
18
+ TileShape.PieNE,
19
+ ];
20
+
21
+ type TileTemplatesConfig = TileConfig & Required<Pick<TileConfig, "scale">>;
22
+
23
+ const TileTemplates: Record<
24
+ TileShape,
25
+ (tileX: number, tileY: number, tileSize: number, tileConfig: TileTemplatesConfig) => JSX.Element | null
26
+ > = {
27
+ [TileShape.Blank]: () => null,
28
+
29
+ [TileShape.Rand]: (tileX, tileY, tileSize, tileConfig) =>
30
+ TileTemplates[randomSet[Math.floor(Math.random() * randomSet.length)]](tileX, tileY, tileSize, tileConfig),
31
+
32
+ [TileShape.Mosaic]: (tileX, tileY, tileSize, tileConfig) =>
33
+ TileTemplates[Math.random() > 0.8 ? TileShape.Blank : TileShape.Square](tileX, tileY, tileSize, tileConfig),
34
+ [TileShape.MosaicCircle]: (tileX, tileY, tileSize, tileConfig) =>
35
+ TileTemplates[Math.random() > 0.8 ? TileShape.Blank : TileShape.Circle](tileX, tileY, tileSize, tileConfig),
36
+ [TileShape.MosaicDiamond]: (tileX, tileY, tileSize, tileConfig) =>
37
+ TileTemplates[Math.random() > 0.8 ? TileShape.Blank : TileShape.Diamond](tileX, tileY, tileSize, tileConfig),
38
+
39
+ [TileShape.Square]: (tileX, tileY, tileSize, tileConfig) => (
40
+ <rect
41
+ key={`tile-${tileX}-${tileY}`}
42
+ x={tileSize * tileX}
43
+ y={tileSize * tileY}
44
+ width={tileSize * tileConfig.scale}
45
+ height={tileSize * tileConfig.scale}
46
+ className={cn("fill-current stroke-none", tileConfig?.className)}
47
+ />
48
+ ),
49
+
50
+ [TileShape.Circle]: (tileX, tileY, tileSize, tileConfig) => (
51
+ <circle
52
+ key={`tile-${tileX}-${tileY}`}
53
+ cx={tileSize * (tileX + 0.5 * tileConfig.scale)}
54
+ cy={tileSize * (tileY + 0.5 * tileConfig.scale)}
55
+ r={(tileSize / 2) * tileConfig.scale}
56
+ className={cn("fill-current stroke-none", tileConfig?.className)}
57
+ />
58
+ ),
59
+
60
+ [TileShape.Diamond]: (tileX, tileY, tileSize, tileConfig) => (
61
+ <path
62
+ key={`tile-${tileX}-${tileY}`}
63
+ d={`M ${tileSize * (tileX + 0.5 * tileConfig.scale)} ${tileSize * tileY}
64
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + 0.5 * tileConfig.scale)}
65
+ L ${tileSize * (tileX + 0.5 * tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)}
66
+ L ${tileSize * tileX} ${tileSize * (tileY + 0.5 * tileConfig.scale)} Z`}
67
+ className={cn("fill-current stroke-none", tileConfig?.className)}
68
+ />
69
+ ),
70
+ [TileShape.Hexagon]: (tileX, tileY, tileSize, tileConfig) => {
71
+ const side = tileSize / Math.sqrt(3);
72
+ const start = (tileSize - side) / 2;
73
+ return (
74
+ <path
75
+ rotate={45}
76
+ key={`tile-${tileX}-${tileY}`}
77
+ d={`M ${tileSize * tileX + start * tileConfig.scale} ${tileSize * tileY}
78
+ L ${tileSize * tileX + (start + side) * tileConfig.scale} ${tileSize * tileY}
79
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + 0.5 * tileConfig.scale)}
80
+ L ${tileSize * tileX + (start + side) * tileConfig.scale} ${tileSize * (tileY + tileConfig.scale)}
81
+ L ${tileSize * tileX + start * tileConfig.scale} ${tileSize * (tileY + tileConfig.scale)}
82
+ L ${tileSize * tileX} ${tileSize * (tileY + 0.5 * tileConfig.scale)} Z`}
83
+ className={cn("fill-current stroke-none", tileConfig?.className)}
84
+ />
85
+ );
86
+ },
87
+ [TileShape.RandBasic]: (tileX, tileY, tileSize, tileConfig) =>
88
+ TileTemplates[(TileShape.Square + Math.floor(Math.random() * 4)) as TileShape](tileX, tileY, tileSize, tileConfig),
89
+
90
+ [TileShape.TriangleSE]: (tileX, tileY, tileSize, tileConfig) => (
91
+ <path
92
+ key={`tile-${tileX}-${tileY}`}
93
+ d={`M ${tileSize * (tileX + tileConfig.scale)} ${tileSize * tileY}
94
+ L ${tileSize * tileX} ${tileSize * (tileY + tileConfig.scale)}
95
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)} Z`}
96
+ className={cn("fill-current stroke-none", tileConfig?.className)}
97
+ />
98
+ ),
99
+ [TileShape.TriangleSW]: (tileX, tileY, tileSize, tileConfig) => (
100
+ <path
101
+ key={`tile-${tileX}-${tileY}`}
102
+ d={`M ${tileSize * tileX} ${tileSize * tileY}
103
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)}
104
+ L ${tileSize * tileX} ${tileSize * (tileY + tileConfig.scale)} Z`}
105
+ className={cn("fill-current stroke-none", tileConfig?.className)}
106
+ />
107
+ ),
108
+ [TileShape.TriangleNW]: (tileX, tileY, tileSize, tileConfig) => (
109
+ <path
110
+ key={`tile-${tileX}-${tileY}`}
111
+ d={`M ${tileSize * (tileX + tileConfig.scale)} ${tileSize * tileY}
112
+ L ${tileSize * tileX} ${tileSize * (tileY + tileConfig.scale)}
113
+ L ${tileSize * tileX} ${tileSize * tileY} Z`}
114
+ className={cn("fill-current stroke-none", tileConfig?.className)}
115
+ />
116
+ ),
117
+ [TileShape.TriangleNE]: (tileX, tileY, tileSize, tileConfig) => (
118
+ <path
119
+ key={`tile-${tileX}-${tileY}`}
120
+ d={`M ${tileSize * tileX} ${tileSize * tileY}
121
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)}
122
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * tileY} Z`}
123
+ className={cn("fill-current stroke-none", tileConfig?.className)}
124
+ />
125
+ ),
126
+ [TileShape.TriangleRand]: (tileX, tileY, tileSize, tileConfig) =>
127
+ TileTemplates[(TileShape.TriangleSE + Math.floor(Math.random() * 4)) as TileShape](
128
+ tileX,
129
+ tileY,
130
+ tileSize,
131
+ tileConfig
132
+ ),
133
+
134
+ [TileShape.CaretN]: (tileX, tileY, tileSize, tileConfig) => (
135
+ <path
136
+ key={`tile-${tileX}-${tileY}`}
137
+ d={`M ${tileSize * tileX} ${tileSize * (tileY + tileConfig.scale)}
138
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)}
139
+ L ${tileSize * (tileX + 0.5 * tileConfig.scale)} ${tileSize * tileY} Z`}
140
+ className={cn("fill-current stroke-none", tileConfig?.className)}
141
+ />
142
+ ),
143
+ [TileShape.CaretE]: (tileX, tileY, tileSize, tileConfig) => (
144
+ <path
145
+ key={`tile-${tileX}-${tileY}`}
146
+ d={`M ${tileSize * tileX} ${tileSize * tileY}
147
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + 0.5 * tileConfig.scale)}
148
+ L ${tileSize * tileX} ${tileSize * (tileY + tileConfig.scale)} Z`}
149
+ className={cn("fill-current stroke-none", tileConfig?.className)}
150
+ />
151
+ ),
152
+ [TileShape.CaretS]: (tileX, tileY, tileSize, tileConfig) => (
153
+ <path
154
+ key={`tile-${tileX}-${tileY}`}
155
+ d={`M ${tileSize * tileX} ${tileSize * tileY}
156
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * tileY}
157
+ L ${tileSize * (tileX + 0.5 * tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)} Z`}
158
+ className={cn("fill-current stroke-none", tileConfig?.className)}
159
+ />
160
+ ),
161
+ [TileShape.CaretW]: (tileX, tileY, tileSize, tileConfig) => (
162
+ <path
163
+ key={`tile-${tileX}-${tileY}`}
164
+ d={`M ${tileSize * (tileX + tileConfig.scale)} ${tileSize * tileY}
165
+ L ${tileSize * tileX} ${tileSize * (tileY + 0.5 * tileConfig.scale)}
166
+ L ${tileSize * (tileX + tileConfig.scale)} ${tileSize * (tileY + tileConfig.scale)} Z`}
167
+ className={cn("fill-current stroke-none", tileConfig?.className)}
168
+ />
169
+ ),
170
+ [TileShape.CaretRand]: (tileX, tileY, tileSize, tileConfig) =>
171
+ TileTemplates[(TileShape.CaretN + Math.floor(Math.random() * 4)) as TileShape](tileX, tileY, tileSize, tileConfig),
172
+
173
+ [TileShape.PieSE]: (tileX, tileY, tileSize, tileConfig) => (
174
+ <path
175
+ key={`tile-${tileX}-${tileY}`}
176
+ d={`M ${tileSize * tileConfig.scale + tileSize * tileX} ${tileSize * tileY}
177
+ A ${tileSize * tileConfig.scale} ${tileSize * tileConfig.scale}, 0, 0, 0, ${tileSize * tileX} ${
178
+ tileSize * tileConfig.scale + tileSize * tileY
179
+ }
180
+ L ${tileSize * tileConfig.scale + tileSize * tileX} ${tileSize * tileConfig.scale + tileSize * tileY} Z`}
181
+ className={cn("fill-current stroke-none", tileConfig?.className)}
182
+ />
183
+ ),
184
+ [TileShape.PieSW]: (tileX, tileY, tileSize, tileConfig) => (
185
+ <path
186
+ key={`tile-${tileX}-${tileY}`}
187
+ d={`M ${tileSize * tileConfig.scale + tileSize * tileX} ${tileSize * tileConfig.scale + tileSize * tileY}
188
+ A ${tileSize * tileConfig.scale} ${tileSize * tileConfig.scale}, 0, 0, 0, ${tileSize * tileX} ${tileSize * tileY}
189
+ L ${tileSize * tileX} ${tileSize * tileConfig.scale + tileSize * tileY} Z`}
190
+ className={cn("fill-current stroke-none", tileConfig?.className)}
191
+ />
192
+ ),
193
+ [TileShape.PieNW]: (tileX, tileY, tileSize, tileConfig) => (
194
+ <path
195
+ key={`tile-${tileX}-${tileY}`}
196
+ d={`M ${tileSize * tileX} ${tileSize * tileConfig.scale + tileSize * tileY}
197
+ A ${tileSize * tileConfig.scale} ${tileSize * tileConfig.scale}, 0, 0, 0, ${
198
+ tileSize * tileConfig.scale + tileSize * tileX
199
+ } ${tileSize * tileY}
200
+ L ${tileSize * tileX} ${tileSize * tileY} Z`}
201
+ className={cn("fill-current stroke-none", tileConfig?.className)}
202
+ />
203
+ ),
204
+ [TileShape.PieNE]: (tileX, tileY, tileSize, tileConfig) => (
205
+ <path
206
+ key={`tile-${tileX}-${tileY}`}
207
+ d={`M ${tileSize * tileX} ${tileSize * tileY}
208
+ A ${tileSize * tileConfig.scale} ${tileSize * tileConfig.scale}, 0, 0, 0, ${
209
+ tileSize * tileConfig.scale + tileSize * tileX
210
+ } ${tileSize * tileConfig.scale + tileSize * tileY}
211
+ L ${tileSize * tileConfig.scale + tileSize * tileX} ${tileSize * tileY} Z`}
212
+ className={cn("fill-current stroke-none", tileConfig?.className)}
213
+ />
214
+ ),
215
+ [TileShape.PieRand]: (tileX, tileY, tileSize, tileConfig) =>
216
+ TileTemplates[(TileShape.PieSE + Math.floor(Math.random() * 4)) as TileShape](tileX, tileY, tileSize, tileConfig),
217
+ };
218
+
219
+ export const generateIconPattern = (width: number, height: number, tileConfig: TileShape | TileConfig) => {
220
+ const pattern: (TileShape | TileConfig)[][] = [];
221
+ for (let y = 0; y < height; y++) {
222
+ pattern[y] = [];
223
+ for (let x = 0; x < width; x++) {
224
+ pattern[y].push(tileConfig);
225
+ }
226
+ }
227
+
228
+ return pattern;
229
+ };
230
+
231
+ export const generateTiles = (pattern: (TileShape | TileConfig)[][], tileSize: number, tileClassName?: string) =>
232
+ pattern
233
+ .map((rows, y) =>
234
+ rows.map((item, x) => {
235
+ const tileConfig: TileTemplatesConfig =
236
+ typeof item === "object"
237
+ ? { className: cn(tileClassName, item.className), scale: item.scale ?? 1, shape: item.shape }
238
+ : { className: tileClassName, scale: 1, shape: item };
239
+
240
+ return TileTemplates[tileConfig.shape](x, y, tileSize, tileConfig);
241
+ })
242
+ )
243
+ .flat();
@@ -0,0 +1,51 @@
1
+ import { useMemo } from "react";
2
+ import { TileShape, type IconGeneratorProps } from "./types";
3
+ import { generateTiles } from "./generate-tiles";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const defaultIconPattern: TileShape[][] = [
7
+ [TileShape.PieRand, TileShape.PieRand],
8
+ [TileShape.PieRand, TileShape.PieRand],
9
+ ];
10
+
11
+ function IconGenerator({
12
+ pattern = defaultIconPattern,
13
+ tileSize = 64,
14
+ tileClassName,
15
+ tileBgClassName,
16
+ renderChildren,
17
+ ...props
18
+ }: IconGeneratorProps) {
19
+ const patternSize = useMemo(
20
+ () => ({
21
+ width: pattern.reduce((longestRow, row) => Math.max(longestRow, row.length), 0) * tileSize,
22
+ height: pattern.length * tileSize,
23
+ }),
24
+ [pattern, tileSize]
25
+ );
26
+
27
+ const paths = useMemo(() => {
28
+ const tiles = generateTiles(pattern, tileSize, tileClassName);
29
+ if (tileBgClassName)
30
+ tiles.unshift(
31
+ <rect
32
+ key="svg-mask-invert"
33
+ x={0}
34
+ y={0}
35
+ width={patternSize.width}
36
+ height={patternSize.height}
37
+ className={cn("fill-black stroke-2 stroke-black", tileBgClassName)}
38
+ />
39
+ );
40
+
41
+ return tiles;
42
+ }, [pattern, patternSize, tileClassName, tileBgClassName, tileSize]);
43
+
44
+ return (
45
+ <svg width={patternSize.width} height={patternSize.height} viewBox={`0 0 ${patternSize.width} ${patternSize.height}`} overflow="visible" {...props}>
46
+ {renderChildren ? renderChildren(paths, patternSize.width, patternSize.height) : paths}
47
+ </svg>
48
+ );
49
+ }
50
+
51
+ export { IconGenerator };
@@ -0,0 +1,38 @@
1
+ import { useId, useMemo } from "react";
2
+ import type { MaskedImageGeneratorProps } from "./types";
3
+
4
+ import { IconGenerator } from "./icon-generator";
5
+
6
+ const MaskedImageFit = { cover: "slice", contain: "meet", fill: "none" };
7
+ const MaskedImagePosition = { top: "xMidYMin", center: "xMidYMid", bottom: "xMidYMax" };
8
+
9
+ function MaskedImageGenerator({
10
+ src,
11
+ imageFit = "cover",
12
+ imagePosition = "top",
13
+ maskType = "alpha",
14
+ ...props
15
+ }: MaskedImageGeneratorProps) {
16
+ const id = useId();
17
+
18
+ const aspectRatio = useMemo(
19
+ () => (imageFit !== "fill" ? `${MaskedImagePosition[imagePosition]} ${MaskedImageFit[imageFit]}` : "none"),
20
+ [imageFit, imagePosition]
21
+ );
22
+
23
+ return (
24
+ <IconGenerator
25
+ {...props}
26
+ renderChildren={(paths) => (
27
+ <>
28
+ <mask id={`svg-mask${id}`} mask-type={maskType}>
29
+ {paths}
30
+ </mask>
31
+ <image href={src} width="100%" height="100%" mask={`url(#svg-mask${id})`} preserveAspectRatio={aspectRatio} />
32
+ </>
33
+ )}
34
+ />
35
+ );
36
+ }
37
+
38
+ export { MaskedImageGenerator };
@@ -0,0 +1,53 @@
1
+ import type { JSX } from "react";
2
+
3
+ export const TileShape = {
4
+ Blank: 0,
5
+ Rand: 1,
6
+ Mosaic: 2,
7
+ MosaicCircle: 3,
8
+ MosaicDiamond: 4,
9
+ Square: 6,
10
+ Circle: 7,
11
+ Diamond: 8,
12
+ Hexagon: 9,
13
+ RandBasic: 10,
14
+ TriangleSE: 11,
15
+ TriangleSW: 12,
16
+ TriangleNW: 13,
17
+ TriangleNE: 14,
18
+ TriangleRand: 15,
19
+ CaretN: 16,
20
+ CaretE: 17,
21
+ CaretS: 18,
22
+ CaretW: 19,
23
+ CaretRand: 20,
24
+ PieSE: 21,
25
+ PieSW: 22,
26
+ PieNW: 23,
27
+ PieNE: 24,
28
+ PieRand: 25,
29
+ } as const;
30
+
31
+ export type TileShape = (typeof TileShape)[keyof typeof TileShape];
32
+
33
+ export interface TileConfig {
34
+ shape: TileShape;
35
+ scale?: number;
36
+ className?: string;
37
+ }
38
+
39
+ export interface IconGeneratorProps extends Omit<React.ComponentProps<"svg">, "children" | "width" | "height" | "overflow"> {
40
+ pattern?: (TileShape | TileConfig)[][];
41
+ tileSize?: number;
42
+ tileClassName?: string;
43
+ tileBgClassName?: string;
44
+
45
+ renderChildren?: (paths: (JSX.Element | null)[], width: number, height: number) => React.ReactNode;
46
+ }
47
+
48
+ export interface MaskedImageGeneratorProps extends IconGeneratorProps {
49
+ src: string;
50
+ imageFit?: "cover" | "contain" | "fill";
51
+ imagePosition?: "top" | "center" | "bottom";
52
+ maskType?: "alpha" | "luminance";
53
+ }
@@ -0,0 +1,7 @@
1
+ export * from './button.tsx'
2
+ export * from './typography/caption.tsx'
3
+ export * from './card.tsx'
4
+ export * from './tag.tsx'
5
+ export * from './horizontal-list.tsx'
6
+ export * from './icon-generator/icon-generator.tsx'
7
+ export * from './icon-generator/masked-image-generator.tsx'
@@ -0,0 +1,39 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const tagVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-sm text-sm bg-complement-3 text-white [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "",
12
+ button:
13
+ "hover:bg-primary transition-all disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ },
15
+ size: {
16
+ default: "h-8 px-4 py-1.5 has-[>svg]:px-3",
17
+ sm: "h-6 gap-1 px-3 has-[>svg]:px-2.5",
18
+ lg: "scroll-mr-6 text-xl/5 font-medium h-16 px-8 has-[>svg]:px-4",
19
+ icon: "size-8",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ variant: "default",
24
+ size: "default",
25
+ },
26
+ }
27
+ );
28
+
29
+ interface TagProps extends VariantProps<typeof tagVariants>, React.ComponentProps<"div"> {
30
+ asChild?: boolean;
31
+ }
32
+
33
+ function Tag({ className, size, variant, asChild = false, ...props }: TagProps) {
34
+ const Comp = asChild ? Slot : "div";
35
+
36
+ return <Comp data-slot="tag" className={cn(tagVariants({ variant, size, className }))} {...props} />;
37
+ }
38
+
39
+ export { Tag, tagVariants };
@@ -0,0 +1,32 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const captionVariants = cva("tracking-tight text-balance", {
7
+ variants: {
8
+ variant: {
9
+ default: "scroll-mr-5 text-lg/5 font-bold text-secondary dark:text-foreground",
10
+ light: "scroll-mr-4 text-sm/4 font-normal text-primary",
11
+ },
12
+ },
13
+ defaultVariants: {
14
+ variant: "default",
15
+ },
16
+ });
17
+
18
+ function Caption({
19
+ className,
20
+ variant,
21
+ asChild = false,
22
+ ...props
23
+ }: React.ComponentProps<"div"> &
24
+ VariantProps<typeof captionVariants> & {
25
+ asChild?: boolean;
26
+ }) {
27
+ const Comp = asChild ? Slot : "div";
28
+
29
+ return <Comp data-slot="caption" className={cn(captionVariants({ variant, className }))} {...props} />;
30
+ }
31
+
32
+ export { Caption, captionVariants };
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,12 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+
4
+ import App from "./App.tsx";
5
+
6
+ import "./index.css";
7
+
8
+ createRoot(document.getElementById("root")!).render(
9
+ <StrictMode>
10
+ <App />
11
+ </StrictMode>
12
+ );