southpaw 0.2.7

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 (169) hide show
  1. package/README.md +51 -0
  2. package/api/client.ts +83 -0
  3. package/api/helpers/fs.ts +98 -0
  4. package/api/helpers/stream.ts +6 -0
  5. package/api/index.ts +50 -0
  6. package/api/server.ts +566 -0
  7. package/constants/fonts.ts +1583 -0
  8. package/constants/icons.ts +2124 -0
  9. package/constants/langs.ts +2592 -0
  10. package/constants/ssr-loading.tsx +17 -0
  11. package/contexts/dev.tsx +124 -0
  12. package/hooks/useDebounceCb.ts +21 -0
  13. package/modules/app.tsx +102 -0
  14. package/modules/behaviors.ts +161 -0
  15. package/modules/component.tsx +483 -0
  16. package/modules/config/index.ts +8 -0
  17. package/modules/database.ts +25 -0
  18. package/modules/document.tsx +23 -0
  19. package/modules/error.tsx +16 -0
  20. package/modules/page/autozoom.tsx +92 -0
  21. package/modules/page/browser.tsx +166 -0
  22. package/modules/page/index.tsx +327 -0
  23. package/modules/page/insert-modal.tsx +225 -0
  24. package/modules/page/inspect/index.tsx +237 -0
  25. package/modules/page/inspect/wrapper.tsx +88 -0
  26. package/modules/page/knobs/action.tsx +58 -0
  27. package/modules/page/knobs/color.tsx +78 -0
  28. package/modules/page/knobs/helpers.ts +71 -0
  29. package/modules/page/knobs/icon.tsx +85 -0
  30. package/modules/page/knobs/linker.tsx +188 -0
  31. package/modules/page/knobs/list-json.tsx +149 -0
  32. package/modules/page/knobs/list-text.tsx +129 -0
  33. package/modules/page/knobs/number.tsx +84 -0
  34. package/modules/page/knobs/segmented.tsx +81 -0
  35. package/modules/page/knobs/select.tsx +89 -0
  36. package/modules/page/knobs/size.tsx +106 -0
  37. package/modules/page/knobs/switch.tsx +75 -0
  38. package/modules/page/knobs/text.tsx +93 -0
  39. package/modules/page/left-tabs.tsx +36 -0
  40. package/modules/page/right-tabs.tsx +31 -0
  41. package/modules/page/seo.tsx +69 -0
  42. package/modules/page/tree.tsx +860 -0
  43. package/modules/state.ts +787 -0
  44. package/modules/store.ts +37 -0
  45. package/modules/theme.ts +105 -0
  46. package/modules/translations.ts +15 -0
  47. package/package.json +68 -0
  48. package/scripts/bin.js +607 -0
  49. package/scripts/test.js +145 -0
  50. package/themes/base.tsx +8962 -0
  51. package/themes/index.ts +4 -0
  52. package/themes/m2.ts +39 -0
  53. package/themes/newspaper.ts +39 -0
  54. package/types/api.ts +57 -0
  55. package/types/fiber_node.ts +69 -0
  56. package/types/react_element.ts +14 -0
  57. package/ui/accordion.tsx +70 -0
  58. package/ui/actionicon.tsx +41 -0
  59. package/ui/affix.tsx +14 -0
  60. package/ui/alert.tsx +37 -0
  61. package/ui/anchor.tsx +52 -0
  62. package/ui/arrow.tsx +116 -0
  63. package/ui/aspectratio.tsx +21 -0
  64. package/ui/autocomplete.tsx +34 -0
  65. package/ui/avatar.tsx +36 -0
  66. package/ui/backgroundimage.tsx +16 -0
  67. package/ui/badge.tsx +28 -0
  68. package/ui/blockquote.tsx +15 -0
  69. package/ui/blocks/faq.tsx +145 -0
  70. package/ui/blocks/features.tsx +167 -0
  71. package/ui/blocks/footer.tsx +182 -0
  72. package/ui/blocks/herobanner.tsx +391 -0
  73. package/ui/blocks/index.ts +44 -0
  74. package/ui/blocks/leftnav.tsx +141 -0
  75. package/ui/blocks/pricing.tsx +210 -0
  76. package/ui/blocks/testimonials.tsx +149 -0
  77. package/ui/blocks/uidocs.tsx +1429 -0
  78. package/ui/box.tsx +27 -0
  79. package/ui/breadcrumbs.tsx +33 -0
  80. package/ui/burger.tsx +23 -0
  81. package/ui/button.tsx +85 -0
  82. package/ui/card.tsx +28 -0
  83. package/ui/center.tsx +26 -0
  84. package/ui/checkbox.tsx +35 -0
  85. package/ui/chip.tsx +32 -0
  86. package/ui/closebutton.tsx +15 -0
  87. package/ui/code.tsx +104 -0
  88. package/ui/collapse.tsx +16 -0
  89. package/ui/colorinput.tsx +32 -0
  90. package/ui/colorpicker.tsx +26 -0
  91. package/ui/colorswatch.tsx +17 -0
  92. package/ui/combobox.tsx +80 -0
  93. package/ui/container.tsx +27 -0
  94. package/ui/dialog.tsx +15 -0
  95. package/ui/divider.tsx +58 -0
  96. package/ui/drawer.tsx +28 -0
  97. package/ui/fieldset.tsx +15 -0
  98. package/ui/filebutton.tsx +23 -0
  99. package/ui/fileinput.tsx +36 -0
  100. package/ui/flex.tsx +139 -0
  101. package/ui/focustrap.tsx +15 -0
  102. package/ui/form.tsx +26 -0
  103. package/ui/grid.tsx +69 -0
  104. package/ui/highlight.tsx +15 -0
  105. package/ui/hoverable.tsx +66 -0
  106. package/ui/hovercard.tsx +45 -0
  107. package/ui/icon.tsx +76 -0
  108. package/ui/image.tsx +32 -0
  109. package/ui/index.ts +1355 -0
  110. package/ui/indicator.tsx +42 -0
  111. package/ui/infobox.tsx +200 -0
  112. package/ui/input.tsx +18 -0
  113. package/ui/inviewport.tsx +23 -0
  114. package/ui/jsoninput.tsx +35 -0
  115. package/ui/kbd.tsx +20 -0
  116. package/ui/list.tsx +33 -0
  117. package/ui/loader.tsx +26 -0
  118. package/ui/loadingoverlay.tsx +22 -0
  119. package/ui/mark.tsx +15 -0
  120. package/ui/menu.tsx +89 -0
  121. package/ui/modal.tsx +27 -0
  122. package/ui/multiselect.tsx +42 -0
  123. package/ui/nativeselect.tsx +17 -0
  124. package/ui/navlink.tsx +40 -0
  125. package/ui/notification.tsx +25 -0
  126. package/ui/numberformatter.tsx +15 -0
  127. package/ui/numberinput.tsx +37 -0
  128. package/ui/overlay.tsx +15 -0
  129. package/ui/pagination.tsx +21 -0
  130. package/ui/paper.tsx +33 -0
  131. package/ui/passwordinput.tsx +36 -0
  132. package/ui/pill.tsx +17 -0
  133. package/ui/pillsinput.tsx +37 -0
  134. package/ui/pininput.tsx +30 -0
  135. package/ui/popover.tsx +43 -0
  136. package/ui/portal.tsx +15 -0
  137. package/ui/progress.tsx +49 -0
  138. package/ui/props.ts +91 -0
  139. package/ui/propsDef.ts +38 -0
  140. package/ui/radio.tsx +30 -0
  141. package/ui/rating.tsx +25 -0
  142. package/ui/ringprogress.tsx +17 -0
  143. package/ui/scrollarea.tsx +27 -0
  144. package/ui/segmentedcontrol.tsx +38 -0
  145. package/ui/select.tsx +43 -0
  146. package/ui/simplegrid.tsx +23 -0
  147. package/ui/skeleton.tsx +15 -0
  148. package/ui/slider.tsx +31 -0
  149. package/ui/space.tsx +19 -0
  150. package/ui/spoiler.tsx +23 -0
  151. package/ui/stepper.tsx +63 -0
  152. package/ui/switch.tsx +29 -0
  153. package/ui/table.tsx +93 -0
  154. package/ui/tabs.tsx +80 -0
  155. package/ui/tagsinput.tsx +19 -0
  156. package/ui/terminal.tsx +44 -0
  157. package/ui/text.tsx +168 -0
  158. package/ui/textarea.tsx +36 -0
  159. package/ui/textinput.tsx +34 -0
  160. package/ui/timeline.tsx +30 -0
  161. package/ui/title.tsx +42 -0
  162. package/ui/tooltip.tsx +32 -0
  163. package/ui/transition.tsx +21 -0
  164. package/ui/tree.tsx +42 -0
  165. package/ui/typographystylesprovider.tsx +20 -0
  166. package/ui/unstyledbutton.tsx +18 -0
  167. package/ui/video.tsx +16 -0
  168. package/ui/visuallyhidden.tsx +15 -0
  169. package/utils/index.ts +76 -0
@@ -0,0 +1,391 @@
1
+ import { create_component } from "southpaw/modules/component";
2
+ import { UI } from "..";
3
+
4
+ type Props = {
5
+ title?: string;
6
+ subtitle?: string;
7
+ description?: string;
8
+ infoText?: string;
9
+ primaryCta?: string;
10
+ secondaryCta?: string;
11
+ bullets?: string[];
12
+ backgroundImage?: string;
13
+ backgroundColor?: string;
14
+ textColor?: string;
15
+ highlightColor?: string;
16
+ showPattern?: boolean;
17
+ alignment?: "left" | "center" | "right";
18
+ size?: "sm" | "md" | "lg" | "xl";
19
+ layout?: "single" | "split";
20
+ imageUrl?: string;
21
+ };
22
+
23
+ const Component = create_component({
24
+ name: "HeroBanner",
25
+ description:
26
+ "A versatile hero banner with gradient background, call-to-action buttons, optional bullet points, and split layout support",
27
+ props: {
28
+ title: "Build Something Amazing",
29
+ subtitle: "The Future is Here",
30
+ description:
31
+ "Transform your ideas into reality with our cutting-edge platform. Start building today and see the difference innovation makes.",
32
+ primaryCta: "Get Started",
33
+ secondaryCta: "Learn More",
34
+ bullets: [
35
+ "TypeScript based – build type safe applications, all components and hooks export types",
36
+ "Free and open source – all packages have MIT license, you can use in any project",
37
+ "No annoying focus ring – focus ring will appear only when user navigates with keyboard",
38
+ ],
39
+ backgroundImage: "",
40
+ showPattern: true,
41
+ alignment: "center",
42
+ size: "lg",
43
+ layout: "single",
44
+ imageUrl: "https://placehold.co/400x400",
45
+ infoText: "Trusted by 10,000+ developers",
46
+ } as Props,
47
+ propsDef: {
48
+ title: {
49
+ type: "text",
50
+ label: "Main Title",
51
+ },
52
+ subtitle: {
53
+ type: "text",
54
+ label: "Subtitle",
55
+ },
56
+ description: {
57
+ type: "text",
58
+ label: "Description",
59
+ },
60
+ primaryCta: {
61
+ type: "text",
62
+ label: "Primary CTA Text",
63
+ },
64
+ secondaryCta: {
65
+ type: "text",
66
+ label: "Secondary CTA Text",
67
+ },
68
+ bullets: {
69
+ type: "list-text",
70
+ label: "Bullet Points",
71
+ },
72
+ infoText: {
73
+ type: "text",
74
+ label: "Info Text",
75
+ },
76
+ backgroundImage: {
77
+ type: "text",
78
+ label: "Background Image URL",
79
+ },
80
+ backgroundColor: {
81
+ type: "color",
82
+ label: "Background Color",
83
+ },
84
+ textColor: {
85
+ type: "color",
86
+ label: "Text Color",
87
+ },
88
+ highlightColor: {
89
+ type: "color",
90
+ label: "Text Color",
91
+ },
92
+ showPattern: {
93
+ type: "switch",
94
+ label: "Show Pattern Overlay",
95
+ },
96
+ alignment: {
97
+ type: "select",
98
+ label: "Text Alignment",
99
+ options: [
100
+ { value: "left", label: "Left" },
101
+ { value: "center", label: "Center" },
102
+ { value: "right", label: "Right" },
103
+ ],
104
+ },
105
+ size: {
106
+ type: "select",
107
+ label: "Hero Size",
108
+ options: [
109
+ { value: "sm", label: "Small" },
110
+ { value: "md", label: "Medium" },
111
+ { value: "lg", label: "Large" },
112
+ { value: "xl", label: "Extra Large" },
113
+ ],
114
+ },
115
+ layout: {
116
+ type: "select",
117
+ label: "Layout Type",
118
+ options: [
119
+ { value: "single", label: "Single Column" },
120
+ { value: "split", label: "Split with Image" },
121
+ ],
122
+ },
123
+ imageUrl: {
124
+ type: "text",
125
+ label: "Image URL (for split layout)",
126
+ },
127
+ },
128
+ render() {
129
+ const {
130
+ title,
131
+ subtitle,
132
+ description,
133
+ primaryCta,
134
+ secondaryCta,
135
+ bullets,
136
+ backgroundImage,
137
+ backgroundColor,
138
+ textColor,
139
+ highlightColor,
140
+ showPattern,
141
+ alignment,
142
+ size,
143
+ layout,
144
+ imageUrl,
145
+ infoText,
146
+ } = this.props as Required<Props>;
147
+
148
+ const sizeProps = {
149
+ sm: { py: 60, titleSize: 32, subtitleSize: "sm", descSize: "md" },
150
+ md: { py: 80, titleSize: 42, subtitleSize: "md", descSize: "lg" },
151
+ lg: { py: 120, titleSize: 52, subtitleSize: "lg", descSize: "xl" },
152
+ xl: { py: 160, titleSize: 62, subtitleSize: "xl", descSize: "xl" },
153
+ }[size];
154
+
155
+ const alignItems = {
156
+ left: "flex-start",
157
+ center: "center",
158
+ right: "flex-end",
159
+ }[alignment];
160
+
161
+ const textAlign = alignment;
162
+
163
+ const renderContent = () => (
164
+ <UI.flex
165
+ direction="column"
166
+ align={alignItems}
167
+ gap={24}
168
+ maw={layout === "split" ? undefined : 800}
169
+ mx={alignment === "center" && layout === "single" ? "auto" : undefined}
170
+ ml={alignment === "left" ? 0 : undefined}
171
+ mr={alignment === "right" ? 0 : undefined}
172
+ flex={layout === "split" ? 1 : undefined}
173
+ >
174
+ {/* Subtitle */}
175
+ {subtitle && (
176
+ <UI.badge
177
+ variant="light"
178
+ size={sizeProps.subtitleSize}
179
+ radius="xl"
180
+ c={textColor}
181
+ style={{
182
+ backgroundColor:
183
+ layout === "single"
184
+ ? "rgba(255,255,255,0.2)"
185
+ : "rgba(59, 130, 246, 0.1)",
186
+ backdropFilter: layout === "single" ? "blur(10px)" : undefined,
187
+ border:
188
+ layout === "single"
189
+ ? "1px solid rgba(255,255,255,0.3)"
190
+ : "1px solid rgba(59, 130, 246, 0.2)",
191
+ }}
192
+ >
193
+ {subtitle}
194
+ </UI.badge>
195
+ )}
196
+
197
+ {/* Main Title */}
198
+ <UI.title
199
+ text={title}
200
+ order={1}
201
+ fz={sizeProps.titleSize}
202
+ fw={700}
203
+ lh={1.1}
204
+ ta={textAlign}
205
+ style={{
206
+ textShadow:
207
+ backgroundImage && layout === "single"
208
+ ? "2px 2px 4px rgba(0,0,0,0.5)"
209
+ : undefined,
210
+ }}
211
+ />
212
+
213
+ {/* Description */}
214
+ <UI.text
215
+ text={description}
216
+ size={sizeProps.descSize}
217
+ ta={textAlign}
218
+ opacity={0.9}
219
+ maw={layout === "split" ? undefined : 600}
220
+ lh={1.6}
221
+ style={{
222
+ textShadow:
223
+ backgroundImage && layout === "single"
224
+ ? "1px 1px 2px rgba(0,0,0,0.5)"
225
+ : undefined,
226
+ }}
227
+ />
228
+
229
+ {/* Bullet Points */}
230
+ {bullets && bullets.length > 0 && (
231
+ <UI.list
232
+ items={bullets.map((bullet, index) => ({
233
+ children: (
234
+ <UI.flex key={index} align="center" gap={12} mb={16}>
235
+ <UI.icon icon="add_circle" c={highlightColor} />
236
+ <UI.text text={bullet} lh={1.6} style={{ flex: 1 }} />
237
+ </UI.flex>
238
+ ),
239
+ }))}
240
+ listStyleType="none"
241
+ withPadding={false}
242
+ />
243
+ )}
244
+
245
+ {/* CTA Buttons */}
246
+ <UI.flex
247
+ gap={16}
248
+ mt={16}
249
+ direction={{ base: "column", sm: "row" }}
250
+ align={alignment === "center" ? "center" : "flex-start"}
251
+ justify={
252
+ alignment === "center"
253
+ ? "center"
254
+ : alignment === "right"
255
+ ? "flex-end"
256
+ : "flex-start"
257
+ }
258
+ >
259
+ {primaryCta && (
260
+ <UI.button
261
+ size="lg"
262
+ radius="md"
263
+ fw={600}
264
+ px={32}
265
+ color={highlightColor}
266
+ style={{
267
+ boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
268
+ transition: "all 0.3s ease",
269
+ }}
270
+ >
271
+ {primaryCta}
272
+ </UI.button>
273
+ )}
274
+
275
+ {secondaryCta && (
276
+ <UI.button
277
+ size="lg"
278
+ radius="md"
279
+ variant="outline"
280
+ color={highlightColor}
281
+ fw={500}
282
+ px={32}
283
+ style={
284
+ layout === "single"
285
+ ? {
286
+ borderColor: "rgba(255,255,255,0.5)",
287
+ backgroundColor: "rgba(255,255,255,0.1)",
288
+ backdropFilter: "blur(10px)",
289
+ transition: "all 0.3s ease",
290
+ }
291
+ : {
292
+ transition: "all 0.3s ease",
293
+ }
294
+ }
295
+ >
296
+ {secondaryCta}
297
+ </UI.button>
298
+ )}
299
+ </UI.flex>
300
+
301
+ {/* Info Text */}
302
+ {infoText && (
303
+ <UI.flex gap={8} mt={32} opacity={0.7} justify={alignment}>
304
+ <UI.icon icon="star" fz={16} />
305
+ <UI.text size="sm" fw={500} text={infoText} />
306
+ </UI.flex>
307
+ )}
308
+ </UI.flex>
309
+ );
310
+
311
+ return (
312
+ <UI.box
313
+ pos="relative"
314
+ py={sizeProps.py}
315
+ px={40}
316
+ c={textColor}
317
+ style={{
318
+ backgroundColor,
319
+ backgroundImage:
320
+ backgroundImage && layout === "single"
321
+ ? `url(${backgroundImage})`
322
+ : undefined,
323
+ backgroundSize: "cover",
324
+ backgroundPosition: "center",
325
+ backgroundRepeat: "no-repeat",
326
+ overflow: "hidden",
327
+ minHeight: layout === "split" ? "100vh" : undefined,
328
+ }}
329
+ >
330
+ {/* Pattern Overlay - only for single layout */}
331
+ {showPattern && layout === "single" && (
332
+ <UI.box
333
+ pos="absolute"
334
+ top={0}
335
+ left={0}
336
+ right={0}
337
+ bottom={0}
338
+ style={{
339
+ background: `
340
+ /* Geometric grid with glow */
341
+ repeating-linear-gradient(90deg, transparent, transparent 80px, rgba(255,255,255,0.06) 82px, rgba(255,255,255,0.02) 84px, transparent 120px),
342
+ repeating-linear-gradient(0deg, transparent, transparent 80px, rgba(255,255,255,0.06) 82px, rgba(255,255,255,0.02) 84px, transparent 120px),
343
+
344
+ /* Corner highlights */
345
+ radial-gradient(ellipse 400px 200px at 0% 0%, rgba(255,255,255,0.08) 0%, transparent 50%),
346
+ radial-gradient(ellipse 400px 200px at 100% 100%, rgba(255,255,255,0.08) 0%, transparent 50%)
347
+ `,
348
+ pointerEvents: "none",
349
+ }}
350
+ >
351
+ {null}
352
+ </UI.box>
353
+ )}
354
+
355
+ {/* Gradient Overlay for better text readability - only for single layout with background image */}
356
+ {backgroundImage && layout === "single" && (
357
+ <UI.box
358
+ pos="absolute"
359
+ top={0}
360
+ left={0}
361
+ right={0}
362
+ bottom={0}
363
+ style={{
364
+ background: `linear-gradient(135deg, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0.3) 100%)`,
365
+ pointerEvents: "none",
366
+ }}
367
+ >
368
+ {null}
369
+ </UI.box>
370
+ )}
371
+
372
+ <UI.container
373
+ size={layout === "split" ? "xl" : "lg"}
374
+ pos="relative"
375
+ style={{ zIndex: 1 }}
376
+ >
377
+ {layout === "split" ? (
378
+ <UI.flex wrap="wrap" gap="xl" c="dark">
379
+ {renderContent()}
380
+ <UI.image flex={1} miw={400} alt="Hero image" src={imageUrl} />
381
+ </UI.flex>
382
+ ) : (
383
+ renderContent()
384
+ )}
385
+ </UI.container>
386
+ </UI.box>
387
+ );
388
+ },
389
+ });
390
+
391
+ export default Component;
@@ -0,0 +1,44 @@
1
+ import dynamic from "next/dynamic";
2
+
3
+ export const Blocks = {
4
+ herobanner: (() => {
5
+ const Comp = dynamic(() => import("./herobanner"));
6
+ Comp.displayName = "Blocks.herobanner";
7
+ return Comp;
8
+ })(),
9
+ features: (() => {
10
+ const Comp = dynamic(() => import("./features"));
11
+ Comp.displayName = "Blocks.features";
12
+ return Comp;
13
+ })(),
14
+ pricing: (() => {
15
+ const Comp = dynamic(() => import("./pricing"));
16
+ Comp.displayName = "Blocks.pricing";
17
+ return Comp;
18
+ })(),
19
+ testimonials: (() => {
20
+ const Comp = dynamic(() => import("./testimonials"));
21
+ Comp.displayName = "Blocks.testimonials";
22
+ return Comp;
23
+ })(),
24
+ faq: (() => {
25
+ const Comp = dynamic(() => import("./faq"));
26
+ Comp.displayName = "Blocks.faq";
27
+ return Comp;
28
+ })(),
29
+ footer: (() => {
30
+ const Comp = dynamic(() => import("./footer"));
31
+ Comp.displayName = "Blocks.footer";
32
+ return Comp;
33
+ })(),
34
+ leftnav: (() => {
35
+ const Comp = dynamic(() => import("./leftnav"));
36
+ Comp.displayName = "Blocks.leftnav";
37
+ return Comp;
38
+ })(),
39
+ uidocs: (() => {
40
+ const Comp = dynamic(() => import("./uidocs"));
41
+ Comp.displayName = "Blocks.uidocs";
42
+ return Comp;
43
+ })(),
44
+ };
@@ -0,0 +1,141 @@
1
+ import { ListItemProps } from "@mantine/core";
2
+ import { Fragment } from "react";
3
+ import { create_component } from "southpaw/modules/component";
4
+ import { UI } from "..";
5
+
6
+ type NavChild = {
7
+ label: string;
8
+ href: string; // base path (ex: "/ui")
9
+ section?: string; // query section (ex: "accordion")
10
+ };
11
+
12
+ type Props = {
13
+ items: {
14
+ title: string;
15
+ href?: string;
16
+ children?: NavChild[];
17
+ }[];
18
+ onItemClick?: () => void;
19
+ };
20
+
21
+ const Component = create_component({
22
+ name: "Leftnav",
23
+ description: "A leftnav",
24
+ props: {
25
+ items: [
26
+ {
27
+ title: "Getting started",
28
+ href: "/getting-started",
29
+ children: [
30
+ {
31
+ label: "Create project",
32
+ href: "/getting-started",
33
+ section: "create-project",
34
+ },
35
+ {
36
+ label: "Create component",
37
+ href: "/getting-started",
38
+ section: "create-component",
39
+ },
40
+ {
41
+ label: "Create page",
42
+ href: "/getting-started",
43
+ section: "create-page",
44
+ },
45
+ {
46
+ label: "Create app",
47
+ href: "/getting-started",
48
+ section: "create-app",
49
+ },
50
+ ],
51
+ },
52
+ ],
53
+ } as Props,
54
+ methods: {
55
+ getHrefSection(href: string) {
56
+ if (!href.includes("?")) return undefined;
57
+ const search = href.split("?")[1];
58
+ const params = new URLSearchParams(search);
59
+ return params.get("section");
60
+ },
61
+ },
62
+ render() {
63
+ const { items } = this.props as Required<Props>;
64
+ const section = this.methods.getHrefSection(this.router.asPath);
65
+
66
+ return (
67
+ <UI.flex
68
+ bg="var(--mantine-color-body)"
69
+ h="100%"
70
+ py="lg"
71
+ direction="column"
72
+ align="flex-start"
73
+ style={{ overflowY: "auto", overflowX: "hidden" }}
74
+ >
75
+ {items.map((item) => {
76
+ const children: ListItemProps[] = (item.children || []).map(
77
+ (child) => {
78
+ const isActive =
79
+ section === this.methods.getHrefSection(child.href);
80
+
81
+ const href = child.section
82
+ ? `${child.href}?section=${child.section}`
83
+ : child.href;
84
+
85
+ return {
86
+ children: (
87
+ <UI.anchor
88
+ key={child.label}
89
+ href={href}
90
+ c={
91
+ isActive
92
+ ? "var(--mantine-primary-color-filled)"
93
+ : "var(--text-color)"
94
+ }
95
+ onClick={() => this.props.onItemClick?.()}
96
+ >
97
+ <UI.text text={child.label} />
98
+ </UI.anchor>
99
+ ),
100
+ };
101
+ },
102
+ );
103
+
104
+ if (item.href) {
105
+ const isActive = section === this.methods.getHrefSection(item.href);
106
+
107
+ return (
108
+ <Fragment key={item.title}>
109
+ <UI.anchor
110
+ href={item.href}
111
+ onClick={() => this.props.onItemClick?.()}
112
+ >
113
+ <UI.text
114
+ fw="bold"
115
+ text={item.title}
116
+ c={
117
+ isActive
118
+ ? "var(--mantine-primary-color-filled)"
119
+ : "var(--text-color)"
120
+ }
121
+ />
122
+ </UI.anchor>
123
+
124
+ <UI.list items={children} listStyleType="none" withPadding />
125
+ </Fragment>
126
+ );
127
+ }
128
+
129
+ return (
130
+ <Fragment key={item.title}>
131
+ <UI.text fw="bold" text={item.title} />
132
+ <UI.list items={children} listStyleType="none" withPadding />
133
+ </Fragment>
134
+ );
135
+ })}
136
+ </UI.flex>
137
+ );
138
+ },
139
+ });
140
+
141
+ export default Component;