retail-design-system 1.0.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 (110) hide show
  1. package/.github/workflows/release.yml +46 -0
  2. package/.oxfmtrc.json +17 -0
  3. package/.oxlintrc.json +132 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/.vscode/settings.json +13 -0
  6. package/README.md +56 -0
  7. package/apps/storybook/.storybook/main.ts +8 -0
  8. package/apps/storybook/.storybook/preview.css +9 -0
  9. package/apps/storybook/.storybook/preview.ts +6 -0
  10. package/apps/storybook/package.json +24 -0
  11. package/apps/storybook/stories/button.stories.ts +118 -0
  12. package/apps/storybook/stories/input.stories.ts +127 -0
  13. package/apps/storybook/stories/label.stories.ts +98 -0
  14. package/apps/storybook/tsconfig.app.json +24 -0
  15. package/apps/storybook/tsconfig.json +4 -0
  16. package/apps/storybook/tsconfig.node.json +22 -0
  17. package/apps/storybook/vite.config.ts +15 -0
  18. package/apps/web/app/(sidebar)/components/[...slugs]/get-child-block.ts +17 -0
  19. package/apps/web/app/(sidebar)/components/[...slugs]/get-component-page-match.ts +56 -0
  20. package/apps/web/app/(sidebar)/components/[...slugs]/get-direct-child-block.ts +22 -0
  21. package/apps/web/app/(sidebar)/components/[...slugs]/layout.tsx +25 -0
  22. package/apps/web/app/(sidebar)/components/[...slugs]/page.tsx +32 -0
  23. package/apps/web/app/(sidebar)/components/[...slugs]/pascal-to-kebab-case.ts +9 -0
  24. package/apps/web/app/(sidebar)/components/button2/page.tsx +154 -0
  25. package/apps/web/app/(sidebar)/components/input/page.tsx +98 -0
  26. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-card-badge.tsx +9 -0
  27. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-coin-active-badge.tsx +14 -0
  28. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-coin-inactive-badge.tsx +12 -0
  29. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-create-coin.tsx +44 -0
  30. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-dialog-icon.tsx +47 -0
  31. package/apps/web/app/(sidebar)/experiments/2025-10-22/page.tsx +167 -0
  32. package/apps/web/app/(sidebar)/experiments/2025-11-04/filters.tsx +90 -0
  33. package/apps/web/app/(sidebar)/experiments/2025-11-04/page.tsx +18 -0
  34. package/apps/web/app/(sidebar)/layout.tsx +17 -0
  35. package/apps/web/app/(sidebar)/primitives/colors/page.tsx +49 -0
  36. package/apps/web/app/favicon.ico +0 -0
  37. package/apps/web/app/layout.tsx +39 -0
  38. package/apps/web/app/page.tsx +14 -0
  39. package/apps/web/app/providers.tsx +15 -0
  40. package/apps/web/components/dialog.tsx +21 -0
  41. package/apps/web/components/logo.tsx +11 -0
  42. package/apps/web/components/logomark.tsx +21 -0
  43. package/apps/web/components/logotype.tsx +25 -0
  44. package/apps/web/components/notion/notion-block-content.tsx +401 -0
  45. package/apps/web/components/notion/notion-docs-blocks.tsx +18 -0
  46. package/apps/web/components/notion/notion-docs-code-page.tsx +20 -0
  47. package/apps/web/components/notion/notion-docs-layout.tsx +52 -0
  48. package/apps/web/components/notion/notion-revalidate-button-client.tsx +14 -0
  49. package/apps/web/components/notion/notion-revalidate-button.tsx +20 -0
  50. package/apps/web/components/notion/notion-rich-text-segments.tsx +55 -0
  51. package/apps/web/components/notion/notion-tabs.tsx +38 -0
  52. package/apps/web/components/notion/notion.ts +223 -0
  53. package/apps/web/components/sidebar-client.tsx +60 -0
  54. package/apps/web/components/sidebar-server.tsx +185 -0
  55. package/apps/web/components/tooltip.tsx +53 -0
  56. package/apps/web/components/topbar.tsx +14 -0
  57. package/apps/web/next.config.ts +10 -0
  58. package/apps/web/package.json +42 -0
  59. package/apps/web/postcss.config.mjs +5 -0
  60. package/apps/web/public/2025-10-22-dialog-banner.png +0 -0
  61. package/apps/web/public/pump-logomark.svg +7 -0
  62. package/apps/web/styles/custom.css +31 -0
  63. package/apps/web/styles/font.css +8 -0
  64. package/apps/web/styles/global.css +5 -0
  65. package/apps/web/styles/tailwind-reset.css +102 -0
  66. package/apps/web/styles/tailwind.css +140 -0
  67. package/apps/web/tsconfig.json +34 -0
  68. package/bun.lock +1249 -0
  69. package/bunfig.toml +2 -0
  70. package/package.json +41 -0
  71. package/packages/ui/global.d.ts +4 -0
  72. package/packages/ui/package.json +49 -0
  73. package/packages/ui/src/components/button/button-spinner.module.css +95 -0
  74. package/packages/ui/src/components/button/button-spinner.tsx +18 -0
  75. package/packages/ui/src/components/button/button.module.css +144 -0
  76. package/packages/ui/src/components/button/button.tsx +102 -0
  77. package/packages/ui/src/components/button-link/button-link.tsx +46 -0
  78. package/packages/ui/src/components/column/column.module.css +4 -0
  79. package/packages/ui/src/components/column/column.tsx +65 -0
  80. package/packages/ui/src/components/row/row.module.css +4 -0
  81. package/packages/ui/src/components/row/row.tsx +65 -0
  82. package/packages/ui/src/components/spacer/spacer.module.css +3 -0
  83. package/packages/ui/src/components/spacer/spacer.tsx +30 -0
  84. package/packages/ui/src/components/switch/switch.module.css +62 -0
  85. package/packages/ui/src/components/switch/switch.tsx +58 -0
  86. package/packages/ui/src/components/tabs/tabs-panel.module.css +4 -0
  87. package/packages/ui/src/components/tabs/tabs-panel.tsx +21 -0
  88. package/packages/ui/src/components/tabs/tabs.module.css +5 -0
  89. package/packages/ui/src/components/tabs/tabs.tsx +21 -0
  90. package/packages/ui/src/components/tabs-underline/tabs-underline-indicator.module.css +10 -0
  91. package/packages/ui/src/components/tabs-underline/tabs-underline-indicator.tsx +33 -0
  92. package/packages/ui/src/components/tabs-underline/tabs-underline-list.module.css +8 -0
  93. package/packages/ui/src/components/tabs-underline/tabs-underline-list.tsx +27 -0
  94. package/packages/ui/src/components/tabs-underline/tabs-underline-tab.module.css +24 -0
  95. package/packages/ui/src/components/tabs-underline/tabs-underline-tab.tsx +30 -0
  96. package/packages/ui/src/foundations/colors/colors.ts +475 -0
  97. package/packages/ui/src/foundations/colors/generate-css.ts +34 -0
  98. package/packages/ui/src/foundations/colors/retail-design-system.css +116 -0
  99. package/packages/ui/src/foundations/colors/tailwind-v3.ts +18 -0
  100. package/packages/ui/src/foundations/colors/tailwind-v4.css +116 -0
  101. package/packages/ui/src/index.ts +34 -0
  102. package/packages/ui/src/input.module.css +57 -0
  103. package/packages/ui/src/input.tsx +49 -0
  104. package/packages/ui/src/label.module.css +8 -0
  105. package/packages/ui/src/label.tsx +23 -0
  106. package/packages/ui/tsconfig.json +14 -0
  107. package/packages/ui/tsup.config.ts +31 -0
  108. package/scripts/clean.sh +69 -0
  109. package/scripts/sort-package-json.sh +30 -0
  110. package/turbo.json +15 -0
package/bunfig.toml ADDED
@@ -0,0 +1,2 @@
1
+ [install]
2
+ linker = "isolated"
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "retail-design-system",
3
+ "workspaces": [
4
+ "apps/*",
5
+ "packages/*"
6
+ ],
7
+ "scripts": {
8
+ "build": "turbo build",
9
+ "check": "bun run check:format && bun run check:lint",
10
+ "check:fix": "bun run check:format:fix && bun run check:lint:fix",
11
+ "check:format": "oxfmt --check",
12
+ "check:format:fix": "oxfmt",
13
+ "check:lint": "oxlint",
14
+ "check:lint:fix": "oxlint --fix --fix-suggestions --fix-dangerously",
15
+ "check:type": "turbo check:type",
16
+ "clean": "sh scripts/clean.sh",
17
+ "dev": "turbo dev",
18
+ "update": "bun update --interactive --recursive"
19
+ },
20
+ "devDependencies": {
21
+ "eslint-plugin-perfectionist": "5.6.0",
22
+ "eslint-plugin-unused-imports": "4.4.1",
23
+ "oxfmt": "0.40.0",
24
+ "oxlint": "1.55.0",
25
+ "turbo": "2.8.17"
26
+ },
27
+ "packageManager": "bun@1.3.0",
28
+ "catalog": {
29
+ "@base-ui/react": "1.3.0",
30
+ "@nattui/tailwind-colors": "0.0.8",
31
+ "@pump-fun/icons-filled": "npm:@central-icons-react/round-filled-radius-1-stroke-2@1.1.158",
32
+ "@pump-fun/icons-line": "npm:@central-icons-react/round-outlined-radius-1-stroke-2@1.1.158",
33
+ "@types/node": "25.5.0",
34
+ "@types/react": "19.2.14",
35
+ "@types/react-dom": "19.2.3",
36
+ "react": "19.2.4",
37
+ "react-dom": "19.2.4",
38
+ "typescript": "5.9.3"
39
+ },
40
+ "version": "1.0.0"
41
+ }
@@ -0,0 +1,4 @@
1
+ declare module "*.module.css" {
2
+ const classes: Record<string, string>
3
+ export default classes
4
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@pump-fun/retail-design-system",
3
+ "version": "0.0.16",
4
+ "description": "A collection of reusable React components built with TypeScript and CSS Modules",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./tailwind-v3/colors": {
19
+ "types": "./dist/foundations/colors/tailwind-v3.d.ts",
20
+ "import": "./dist/foundations/colors/tailwind-v3.js",
21
+ "require": "./dist/foundations/colors/tailwind-v3.cjs"
22
+ },
23
+ "./tailwind-v4/colors.css": "./dist/foundations/colors/tailwind-v4.css",
24
+ "./retail-design-system.css": "./dist/foundations/colors/retail-design-system.css"
25
+ },
26
+ "publishConfig": {
27
+ "access": "restricted"
28
+ },
29
+ "scripts": {
30
+ "build": "bun run src/foundations/colors/generate-css.ts && tsup",
31
+ "check:types": "tsc",
32
+ "dev": "bun run src/foundations/colors/generate-css.ts && tsup --watch",
33
+ "prepublishOnly": "pnpm run build",
34
+ "update": "pnpx npm-check-updates --upgrade"
35
+ },
36
+ "dependencies": {
37
+ "@base-ui/react": "catalog:"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "catalog:",
41
+ "@types/react": "catalog:",
42
+ "tsup": "8.5.1",
43
+ "typescript": "catalog:"
44
+ },
45
+ "peerDependencies": {
46
+ "react": "^18.0.0 || ^19.0.0",
47
+ "react-dom": "^18.0.0 || ^19.0.0"
48
+ }
49
+ }
@@ -0,0 +1,95 @@
1
+ .button_spinner {
2
+ position: relative;
3
+ width: var(--size);
4
+ height: var(--size);
5
+ scale: -1 1;
6
+ animation: spinner 1000ms steps(12, end) infinite;
7
+ }
8
+
9
+ @keyframes spinner {
10
+ to {
11
+ rotate: 1turn;
12
+ }
13
+ }
14
+
15
+ .button_spinner > div {
16
+ position: absolute;
17
+ top: 50%;
18
+ right: 0;
19
+ width: calc(var(--size) / 2);
20
+ height: calc(var(--size) / 12);
21
+ pointer-events: none;
22
+ transform-origin: center left;
23
+ translate: 0 -50%;
24
+ transition: all 150ms;
25
+ }
26
+
27
+ .button_spinner > div::after {
28
+ position: absolute;
29
+ right: 0;
30
+ width: 50%;
31
+ height: 100%;
32
+ content: "";
33
+ background-color: currentColor;
34
+ border-radius: 9999px;
35
+ }
36
+
37
+ .button_spinner > div:nth-child(1) {
38
+ opacity: 1;
39
+ rotate: 0deg;
40
+ }
41
+
42
+ .button_spinner > div:nth-child(2) {
43
+ opacity: 0.925;
44
+ rotate: 30deg;
45
+ }
46
+
47
+ .button_spinner > div:nth-child(3) {
48
+ opacity: 0.85;
49
+ rotate: 60deg;
50
+ }
51
+
52
+ .button_spinner > div:nth-child(4) {
53
+ opacity: 0.775;
54
+ rotate: 90deg;
55
+ }
56
+
57
+ .button_spinner > div:nth-child(5) {
58
+ opacity: 0.7;
59
+ rotate: 120deg;
60
+ }
61
+
62
+ .button_spinner > div:nth-child(6) {
63
+ opacity: 0.625;
64
+ rotate: 150deg;
65
+ }
66
+
67
+ .button_spinner > div:nth-child(7) {
68
+ opacity: 0.55;
69
+ rotate: 180deg;
70
+ }
71
+
72
+ .button_spinner > div:nth-child(8) {
73
+ opacity: 0.475;
74
+ rotate: 210deg;
75
+ }
76
+
77
+ .button_spinner > div:nth-child(9) {
78
+ opacity: 0.4;
79
+ rotate: 240deg;
80
+ }
81
+
82
+ .button_spinner > div:nth-child(10) {
83
+ opacity: 0.325;
84
+ rotate: 270deg;
85
+ }
86
+
87
+ .button_spinner > div:nth-child(11) {
88
+ opacity: 0.25;
89
+ rotate: 300deg;
90
+ }
91
+
92
+ .button_spinner > div:nth-child(12) {
93
+ opacity: 0.15;
94
+ rotate: 330deg;
95
+ }
@@ -0,0 +1,18 @@
1
+ import type { CSSProperties, JSX } from "react"
2
+ import styles from "@/components/button/button-spinner.module.css"
3
+
4
+ export interface ButtonSpinnerProps {
5
+ size?: number
6
+ }
7
+
8
+ export function ButtonSpinner(props: ButtonSpinnerProps): JSX.Element {
9
+ const { size = 16 } = props
10
+
11
+ return (
12
+ <div className={styles.button_spinner} style={{ "--size": `${size}px` } as CSSProperties}>
13
+ {Array.from({ length: 12 }).map((_, index) => (
14
+ <div key={index} />
15
+ ))}
16
+ </div>
17
+ )
18
+ }
@@ -0,0 +1,144 @@
1
+ /* ===================================================== */
2
+ /* Base */
3
+ /* ===================================================== */
4
+ .button {
5
+ align-items: safe center;
6
+ border-style: none;
7
+ column-gap: 8px;
8
+ cursor: pointer;
9
+ display: flex;
10
+ flex-shrink: 0;
11
+ font-family: var(--font-sans, sans-serif);
12
+ font-size: 14px;
13
+ font-weight: 500;
14
+ height: var(--size);
15
+ justify-content: safe center;
16
+ line-height: 1.5;
17
+ outline-color: var(--color-bg-accent);
18
+ outline-offset: 2px;
19
+ outline-style: none;
20
+ outline-width: 2px;
21
+ overflow: hidden;
22
+ transition-duration: 150ms;
23
+ transition-property: background-color, box-shadow, opacity, translate;
24
+ user-select: none;
25
+ }
26
+
27
+ /* When pressed, move the button down */
28
+ .button:active:not(:disabled),
29
+ .button[aria-pressed="true"]:not(:disabled) {
30
+ translate: 0 1px;
31
+ }
32
+
33
+ /* When disabled, disable cursor and reduce opacity */
34
+ .button:disabled {
35
+ cursor: not-allowed;
36
+ opacity: 0.5;
37
+ }
38
+
39
+ /* When keyboard focused, show outline */
40
+ .button:focus-visible {
41
+ outline-style: solid;
42
+ }
43
+
44
+ /* ===================================================== */
45
+ /* Icon only */
46
+ /* ===================================================== */
47
+ .button__icon_only {
48
+ width: var(--size) !important;
49
+ padding: 0 !important;
50
+ }
51
+
52
+ /* ===================================================== */
53
+ /* Rounded */
54
+ /* ===================================================== */
55
+ .button__rounded_base {
56
+ border-radius: 12px;
57
+ }
58
+
59
+ .button__rounded_full {
60
+ border-radius: 9999px !important;
61
+ }
62
+
63
+ /* ===================================================== */
64
+ /* Size */
65
+ /* ===================================================== */
66
+ .button__size_32 {
67
+ --size: 32px;
68
+ padding: 0 10px;
69
+ border-radius: 8px;
70
+ }
71
+
72
+ .button__size_36 {
73
+ --size: 36px;
74
+ padding: 0 12px;
75
+ border-radius: 10px;
76
+ }
77
+
78
+ .button__size_40 {
79
+ --size: 40px;
80
+ padding: 0 14px;
81
+ border-radius: 12px;
82
+ }
83
+
84
+ .button__size_44 {
85
+ --size: 44px;
86
+ padding: 0 16px;
87
+ border-radius: 14px;
88
+ }
89
+
90
+ .button__size_48 {
91
+ --size: 48px;
92
+ padding: 0 18px;
93
+ border-radius: 16px;
94
+ }
95
+
96
+ /* ===================================================== */
97
+ /* Variant */
98
+ /* ===================================================== */
99
+ /* Ghost */
100
+ .button__variant_ghost {
101
+ color: var(--color-text-primary);
102
+ background-color: transparent;
103
+ }
104
+
105
+ .button__variant_ghost:active:not(:disabled),
106
+ .button__variant_ghost:hover:not(:disabled),
107
+ .button__variant_ghost[aria-pressed="true"]:not(:disabled) {
108
+ background-color: color-mix(in oklab, var(--color-bg-tertiary) 75%, transparent 0%);
109
+ }
110
+
111
+ /* Primary */
112
+ .button__variant_primary {
113
+ color: var(--color-text-on-accent);
114
+ background-color: var(--color-bg-accent);
115
+ }
116
+
117
+ .button__variant_primary:active:not(:disabled),
118
+ .button__variant_primary:hover:not(:disabled),
119
+ .button__variant_primary[aria-pressed="true"]:not(:disabled) {
120
+ background-color: color-mix(in oklab, var(--color-bg-accent) 75%, transparent 0%);
121
+ }
122
+
123
+ /* Secondary */
124
+ .button__variant_secondary {
125
+ color: var(--color-text-primary);
126
+ background-color: var(--color-bg-tertiary);
127
+ }
128
+
129
+ .button__variant_secondary:active:not(:disabled),
130
+ .button__variant_secondary:hover:not(:disabled),
131
+ .button__variant_secondary[aria-pressed="true"]:not(:disabled) {
132
+ background-color: color-mix(in oklab, var(--color-bg-tertiary) 75%, transparent 0%);
133
+ }
134
+
135
+ /* ===================================================== */
136
+ /* Width */
137
+ /* ===================================================== */
138
+ .button__width_base {
139
+ width: max-content; /* Fit the button content */
140
+ }
141
+
142
+ .button__width_full {
143
+ width: 100%;
144
+ }
@@ -0,0 +1,102 @@
1
+ import type { ComponentProps, JSX, ReactNode } from "react"
2
+ import { ButtonSpinner } from "@/components/button/button-spinner"
3
+ import styles from "@/components/button/button.module.css"
4
+
5
+ export interface ButtonIconProps extends ButtonInternalProps {
6
+ children?: ReactNode
7
+ iconEnd?: never
8
+ iconStart?: never
9
+ isIconOnly: true
10
+ }
11
+
12
+ export interface ButtonProps extends ButtonInternalProps {
13
+ children?: string | string[]
14
+ isIconOnly?: false
15
+ }
16
+
17
+ interface ButtonInternalProps extends Omit<ComponentProps<"button">, "aria-pressed" | "disabled"> {
18
+ iconEnd?: ReactNode
19
+ iconStart?: ReactNode
20
+ isActive?: boolean
21
+ isDisabled?: boolean
22
+ isFullWidth?: boolean
23
+ isIconOnly?: boolean
24
+ isLoading?: boolean
25
+ isRounded?: boolean
26
+ size?: 32 | 36 | 40 | 44 | 48
27
+ variant?: "ghost" | "primary" | "secondary"
28
+ }
29
+
30
+ type ButtonUnionProps = ButtonIconProps | ButtonProps
31
+
32
+ export function Button(props: ButtonUnionProps): JSX.Element {
33
+ const {
34
+ children = "",
35
+ className: customClassName = "",
36
+ iconEnd = "",
37
+ iconStart = "",
38
+ isActive = false,
39
+ isDisabled = false,
40
+ isFullWidth = false,
41
+ isIconOnly = false,
42
+ isLoading = false,
43
+ isRounded = false,
44
+ size = 40,
45
+ type = "button",
46
+ variant = "primary",
47
+ ...rest
48
+ } = props
49
+
50
+ const combinedClassName = `
51
+ ${BUTTON_CLASS_NAME.BASE}
52
+ ${BUTTON_CLASS_NAME.SIZE[size]}
53
+ ${BUTTON_CLASS_NAME.VARIANT[variant.toUpperCase() as keyof typeof BUTTON_CLASS_NAME.VARIANT]}
54
+ ${isFullWidth ? BUTTON_CLASS_NAME.WIDTH.FULL : BUTTON_CLASS_NAME.WIDTH.BASE}
55
+ ${isIconOnly ? BUTTON_CLASS_NAME.ICON_ONLY : ""}
56
+ ${isRounded ? BUTTON_CLASS_NAME.ROUNDED.FULL : BUTTON_CLASS_NAME.ROUNDED.BASE}
57
+ ${customClassName}
58
+ `
59
+ .replaceAll(/\s+/g, " ")
60
+ .trim()
61
+
62
+ return (
63
+ <button
64
+ aria-pressed={isActive}
65
+ className={combinedClassName}
66
+ disabled={isDisabled || isLoading}
67
+ type={type}
68
+ {...rest}
69
+ >
70
+ {isLoading && <ButtonSpinner />}
71
+ {!isLoading && iconStart}
72
+ {isIconOnly ? isLoading ? undefined : children : <span>{children}</span>}
73
+ {!isLoading && iconEnd}
74
+ </button>
75
+ )
76
+ }
77
+
78
+ export const BUTTON_CLASS_NAME = {
79
+ BASE: styles.button,
80
+ ICON_ONLY: styles.button__icon_only,
81
+ ROUNDED: {
82
+ BASE: styles.button__rounded_base,
83
+ FULL: styles.button__rounded_full,
84
+ },
85
+ SIZE: {
86
+ 32: styles.button__size_32,
87
+ 36: styles.button__size_36,
88
+ 40: styles.button__size_40,
89
+ 44: styles.button__size_44,
90
+ 48: styles.button__size_48,
91
+ },
92
+ VARIANT: {
93
+ ACCENT: styles.button__variant_accent,
94
+ GHOST: styles.button__variant_ghost,
95
+ PRIMARY: styles.button__variant_primary,
96
+ SECONDARY: styles.button__variant_secondary,
97
+ },
98
+ WIDTH: {
99
+ BASE: styles.button__width_base,
100
+ FULL: styles.button__width_full,
101
+ },
102
+ } as const
@@ -0,0 +1,46 @@
1
+ import { createElement, type ComponentProps, type ElementType, type JSX } from "react"
2
+ import { BUTTON_CLASS_NAME, type ButtonProps } from "@/components/button/button"
3
+
4
+ export type ButtonLinkProps<ComponentType extends ElementType = "a"> = ButtonLinkInternalProps &
5
+ ComponentProps<ComponentType>
6
+
7
+ interface ButtonLinkInternalProps extends Pick<
8
+ ButtonProps,
9
+ "isFullWidth" | "isIconOnly" | "isRounded" | "size" | "variant"
10
+ > {
11
+ as?: ElementType
12
+ }
13
+
14
+ export function ButtonLink<ComponentType extends ElementType = "a">(
15
+ props: ButtonLinkProps<ComponentType>,
16
+ ): JSX.Element {
17
+ const {
18
+ as = "a",
19
+ className: customClassName = "",
20
+ isFullWidth = false,
21
+ isIconOnly = false,
22
+ isRounded = false,
23
+ size = 40,
24
+ variant = "primary",
25
+ ...rest
26
+ } = props
27
+
28
+ const Component = as as ElementType
29
+
30
+ const combinedClassName = `
31
+ ${BUTTON_CLASS_NAME.BASE}
32
+ ${BUTTON_CLASS_NAME.SIZE[size]}
33
+ ${BUTTON_CLASS_NAME.VARIANT[variant.toUpperCase() as keyof typeof BUTTON_CLASS_NAME.VARIANT]}
34
+ ${isFullWidth ? BUTTON_CLASS_NAME.WIDTH.FULL : BUTTON_CLASS_NAME.WIDTH.BASE}
35
+ ${isIconOnly ? BUTTON_CLASS_NAME.ICON_ONLY : ""}
36
+ ${isRounded ? BUTTON_CLASS_NAME.ROUNDED.FULL : BUTTON_CLASS_NAME.ROUNDED.BASE}
37
+ ${customClassName}
38
+ `
39
+ .replaceAll(/\s+/g, " ")
40
+ .trim()
41
+
42
+ return createElement(Component, {
43
+ className: combinedClassName,
44
+ ...rest,
45
+ })
46
+ }
@@ -0,0 +1,4 @@
1
+ .column {
2
+ display: flex;
3
+ flex-direction: column;
4
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ createElement,
3
+ type ComponentProps,
4
+ type CSSProperties,
5
+ type ElementType,
6
+ type JSX,
7
+ } from "react"
8
+ import styles from "@/components/column/column.module.css"
9
+
10
+ export type ColumnProps<ComponentType extends ElementType = "div"> = ColumnInternalProps &
11
+ ComponentProps<ComponentType>
12
+
13
+ interface ColumnInternalProps {
14
+ alignItems?: CSSProperties["alignItems"]
15
+ as?: keyof JSX.IntrinsicElements
16
+ flexWrap?: CSSProperties["flexWrap"]
17
+ gap?: CSSProperties["gap"]
18
+ gapX?: CSSProperties["columnGap"]
19
+ gapY?: CSSProperties["rowGap"]
20
+ justifyContent?: CSSProperties["justifyContent"]
21
+ }
22
+
23
+ export function Column(props: ColumnProps): JSX.Element {
24
+ const {
25
+ alignItems = undefined,
26
+ as = "div",
27
+ className: customClassName = "",
28
+ flexWrap = undefined,
29
+ gap = undefined,
30
+ gapX = undefined,
31
+ gapY = undefined,
32
+ justifyContent = undefined,
33
+ style: customStyle,
34
+ ...rest
35
+ } = props
36
+
37
+ const Component = as
38
+
39
+ const combinedClassName = `
40
+ ${COLUMN_CLASS_NAME.BASE}
41
+ ${customClassName}
42
+ `
43
+ .replaceAll(/\s+/g, " ")
44
+ .trim()
45
+
46
+ const combinedStyle = {
47
+ ...customStyle,
48
+ ...(gap === undefined ? {} : { gap }),
49
+ ...(gapX === undefined ? {} : { columnGap: gapX }),
50
+ ...(gapY === undefined ? {} : { rowGap: gapY }),
51
+ ...(flexWrap === undefined ? {} : { flexWrap }),
52
+ ...(alignItems === undefined ? {} : { alignItems }),
53
+ ...(justifyContent === undefined ? {} : { justifyContent }),
54
+ }
55
+
56
+ return createElement(Component, {
57
+ className: combinedClassName,
58
+ style: combinedStyle,
59
+ ...rest,
60
+ })
61
+ }
62
+
63
+ export const COLUMN_CLASS_NAME = {
64
+ BASE: styles.column,
65
+ } as const
@@ -0,0 +1,4 @@
1
+ .row {
2
+ display: flex;
3
+ flex-direction: row;
4
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ createElement,
3
+ type ComponentProps,
4
+ type CSSProperties,
5
+ type ElementType,
6
+ type JSX,
7
+ } from "react"
8
+ import styles from "@/components/row/row.module.css"
9
+
10
+ export type RowProps<ComponentType extends ElementType = "div"> = ComponentProps<ComponentType> &
11
+ RowInternalProps
12
+
13
+ interface RowInternalProps {
14
+ alignItems?: CSSProperties["alignItems"]
15
+ as?: keyof JSX.IntrinsicElements
16
+ flexWrap?: CSSProperties["flexWrap"]
17
+ gap?: CSSProperties["gap"]
18
+ gapX?: CSSProperties["columnGap"]
19
+ gapY?: CSSProperties["rowGap"]
20
+ justifyContent?: CSSProperties["justifyContent"]
21
+ }
22
+
23
+ export function Row(props: RowProps): JSX.Element {
24
+ const {
25
+ alignItems = undefined,
26
+ as = "div",
27
+ className: customClassName = "",
28
+ flexWrap = undefined,
29
+ gap = undefined,
30
+ gapX = undefined,
31
+ gapY = undefined,
32
+ justifyContent = undefined,
33
+ style: customStyle,
34
+ ...rest
35
+ } = props
36
+
37
+ const Component = as
38
+
39
+ const combinedClassName = `
40
+ ${ROW_CLASS_NAME.BASE}
41
+ ${customClassName}
42
+ `
43
+ .replaceAll(/\s+/g, " ")
44
+ .trim()
45
+
46
+ const combinedStyle = {
47
+ ...customStyle,
48
+ ...(gap === undefined ? {} : { gap }),
49
+ ...(gapX === undefined ? {} : { columnGap: gapX }),
50
+ ...(gapY === undefined ? {} : { rowGap: gapY }),
51
+ ...(flexWrap === undefined ? {} : { flexWrap }),
52
+ ...(alignItems === undefined ? {} : { alignItems }),
53
+ ...(justifyContent === undefined ? {} : { justifyContent }),
54
+ }
55
+
56
+ return createElement(Component, {
57
+ className: combinedClassName,
58
+ style: combinedStyle,
59
+ ...rest,
60
+ })
61
+ }
62
+
63
+ export const ROW_CLASS_NAME = {
64
+ BASE: styles.row,
65
+ } as const
@@ -0,0 +1,3 @@
1
+ .spacer {
2
+ flex-shrink: 0;
3
+ }
@@ -0,0 +1,30 @@
1
+ import type { ComponentProps, CSSProperties } from "react"
2
+ import styles from "@/components/spacer/spacer.module.css"
3
+
4
+ export interface SpacerProps extends Omit<ComponentProps<"div">, "children"> {
5
+ height?: CSSProperties["height"]
6
+ width?: CSSProperties["width"]
7
+ }
8
+
9
+ export function Spacer(props: SpacerProps) {
10
+ const { className: customClassName = "", height, style: customStyle, width, ...rest } = props
11
+
12
+ const combinedClassName = `
13
+ ${SPACER_CLASS_NAME.BASE}
14
+ ${customClassName}
15
+ `
16
+ .replaceAll(/\s+/g, " ")
17
+ .trim()
18
+
19
+ const combinedStyle = {
20
+ ...customStyle,
21
+ ...(width === undefined ? {} : { width }),
22
+ ...(height === undefined ? {} : { height }),
23
+ }
24
+
25
+ return <div className={combinedClassName} style={combinedStyle} {...rest} />
26
+ }
27
+
28
+ export const SPACER_CLASS_NAME = {
29
+ BASE: styles.spacer,
30
+ } as const