@nattui/react-components 0.0.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.
@@ -0,0 +1,238 @@
1
+ /* ===================================================== */
2
+ /* Base */
3
+ /* ===================================================== */
4
+ .button {
5
+ display: flex;
6
+ position: relative;
7
+ column-gap: 8px;
8
+ flex-shrink: 0;
9
+ justify-content: safe center;
10
+ align-items: safe center;
11
+ transition-duration: 150ms;
12
+ transition-property: all;
13
+ cursor: pointer;
14
+ outline-width: 0;
15
+ border-width: 1px;
16
+ border-style: solid;
17
+ border-color: transparent;
18
+ overflow: hidden;
19
+ font-weight: 500;
20
+ line-height: 1.5;
21
+ user-select: none;
22
+ }
23
+
24
+ .button:disabled {
25
+ opacity: 0.5;
26
+ cursor: not-allowed;
27
+ }
28
+
29
+ /* Active */
30
+ .button:enabled[aria-pressed="true"] > *,
31
+ .button:enabled:active > * {
32
+ transform: translateY(1px);
33
+ transition-duration: 150ms;
34
+ transition-property: transform;
35
+ }
36
+
37
+ /* Disable child shadow */
38
+ .button:enabled[aria-pressed="true"] > *,
39
+ .button:enabled:active > *,
40
+ .button:enabled:hover > * {
41
+ filter: none !important;
42
+ }
43
+
44
+ /* Hover */
45
+ .button:enabled[aria-pressed="true"],
46
+ .button:enabled:active,
47
+ .button:enabled:hover {
48
+ opacity: 0.85;
49
+ }
50
+
51
+ /* Button background */
52
+ .button:enabled[aria-pressed="true"] > [data-element="button-background"],
53
+ .button:enabled:active > [data-element="button-background"],
54
+ .button:enabled:hover > [data-element="button-background"] {
55
+ opacity: 0;
56
+ }
57
+
58
+ /* ===================================================== */
59
+ /* Icon only */
60
+ /* ===================================================== */
61
+ .button__icon_only {
62
+ padding: 0 !important;
63
+ width: var(--size) !important;
64
+ }
65
+
66
+ /* ===================================================== */
67
+ /* Rounded */
68
+ /* ===================================================== */
69
+ .button__rounded_base {
70
+ border-radius: 8px;
71
+ }
72
+
73
+ .button__rounded_full {
74
+ border-radius: 9999px;
75
+ }
76
+
77
+ /* ===================================================== */
78
+ /* Size */
79
+ /* ===================================================== */
80
+ .button__size_32 {
81
+ --size: 32px;
82
+ padding: 0 8px;
83
+ height: var(--size);
84
+ font-size: 14px;
85
+ }
86
+
87
+ .button__size_36 {
88
+ --size: 36px;
89
+ padding: 0 12px;
90
+ height: var(--size);
91
+ font-size: 14px;
92
+ }
93
+
94
+ .button__size_40 {
95
+ --size: 40px;
96
+ padding: 0 16px;
97
+ height: var(--size);
98
+ font-size: 14px;
99
+ }
100
+
101
+ .button__size_48 {
102
+ --size: 48px;
103
+ padding: 0 20px;
104
+ height: var(--size);
105
+ font-size: 16px;
106
+ }
107
+
108
+ /* ===================================================== */
109
+ /* Variant */
110
+ /* ===================================================== */
111
+ /* Accent */
112
+ .button__variant_accent {
113
+ box-shadow:
114
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.25),
115
+ inset 0 -1px 0 0 rgba(0, 0, 0, 0.1);
116
+ border-color: var(--color-primary-9, #e93d82);
117
+ background-image: linear-gradient(
118
+ to bottom,
119
+ var(--color-primary-9, #e93d82),
120
+ var(--color-primary-10, #e03177)
121
+ );
122
+ color: var(--color-gray-1, #fdfcfd);
123
+ }
124
+
125
+ .button__variant_accent:enabled[aria-pressed="true"],
126
+ .button__variant_accent:enabled:active {
127
+ box-shadow:
128
+ inset 0 -1px 0 0 rgba(255, 255, 255, 0.25),
129
+ inset 0 1px 0 0 rgba(0, 0, 0, 0.1) !important;
130
+ }
131
+
132
+ /* Ghost */
133
+ .button__variant_ghost {
134
+ background-color: transparent;
135
+ color: var(--color-gray-11, #6f6e77);
136
+ }
137
+
138
+ .button__variant_ghost:enabled[aria-pressed="true"],
139
+ .button__variant_ghost:enabled:active {
140
+ background-color: var(--color-gray-5, #e9e8ea) !important;
141
+ color: var(--color-gray-12, #1a1523);
142
+ }
143
+
144
+ .button__variant_ghost:enabled:hover {
145
+ background-color: color-mix(
146
+ in oklab,
147
+ var(--color-gray-5, #e9e8ea) 75%,
148
+ transparent
149
+ );
150
+ color: var(--color-gray-12, #1a1523);
151
+ }
152
+
153
+ /* Primary */
154
+ .button__variant_primary {
155
+ box-shadow:
156
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.25),
157
+ inset 0 -1px 0 0 rgba(0, 0, 0, 0.1);
158
+ border-color: var(--color-gray-12, #1a1523);
159
+ background-image: linear-gradient(
160
+ to bottom,
161
+ color-mix(in oklab, var(--color-gray-12, #1a1523) 90%, transparent),
162
+ var(--color-gray-12, #1a1523)
163
+ );
164
+ color: var(--color-gray-1, #fdfcfd);
165
+ }
166
+
167
+ .button__variant_primary:enabled[aria-pressed="true"],
168
+ .button__variant_primary:enabled:active {
169
+ box-shadow:
170
+ inset 0 -1px 0 0 rgba(255, 255, 255, 0.25),
171
+ inset 0 1px 0 0 rgba(0, 0, 0, 0.5) !important;
172
+ }
173
+
174
+ /* Secondary */
175
+ .button__variant_secondary {
176
+ box-shadow:
177
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.25),
178
+ inset 0 -1px 0 0 rgba(0, 0, 0, 0.1);
179
+ border-color: var(--color-gray-6, #e4e2e4);
180
+ background-image: linear-gradient(
181
+ to bottom,
182
+ var(--color-gray-1, #fdfcfd),
183
+ var(--color-gray-3, #f4f2f4)
184
+ );
185
+ color: var(--color-gray-11, #6f6e77);
186
+ }
187
+
188
+ .button__variant_secondary:enabled[aria-pressed="true"],
189
+ .button__variant_secondary:enabled:active,
190
+ .button__variant_secondary:enabled:hover {
191
+ border-color: var(--color-gray-8, #c8c7cb);
192
+ color: var(--color-gray-12, #1a1523);
193
+ }
194
+
195
+ .button__variant_secondary:enabled[aria-pressed="true"],
196
+ .button__variant_secondary:enabled:active {
197
+ box-shadow:
198
+ inset 0 -1px 0 0 var(--color-gray-1, #fdfcfd),
199
+ inset 0 1px 0 0 rgba(0, 0, 0, 0.1) !important;
200
+ }
201
+
202
+ /* Group */
203
+ .button__variant_accent,
204
+ .button__variant_primary,
205
+ .button__variant_secondary {
206
+ filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.05));
207
+ }
208
+
209
+ .button__variant_accent > * {
210
+ filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.1));
211
+ }
212
+
213
+ .button__variant_primary > * {
214
+ filter: drop-shadow(0 1px 0 rgb(255 255 255 / 0.1));
215
+ }
216
+
217
+ .button__variant_accent:enabled:hover,
218
+ .button__variant_primary:enabled:hover,
219
+ .button__variant_secondary:enabled:hover {
220
+ filter: none;
221
+ box-shadow: inset 0 0 0 0 transparent;
222
+ }
223
+
224
+ .button__variant_ghost:enabled:hover,
225
+ .button__variant_secondary:enabled:hover {
226
+ color: var(--color-gray-12, #1a1523);
227
+ }
228
+
229
+ /* ===================================================== */
230
+ /* Width */
231
+ /* ===================================================== */
232
+ .button__width_base {
233
+ width: max-content;
234
+ }
235
+
236
+ .button__width_full {
237
+ width: 100%;
238
+ }
package/src/button.tsx ADDED
@@ -0,0 +1,97 @@
1
+ import type { ComponentProps, JSX, ReactNode } from "react"
2
+ import { ButtonBackground } from "@/button-background"
3
+ import { ButtonSpinner } from "@/button-spinner"
4
+ import styles from "@/button.module.css"
5
+
6
+ export interface ButtonProps extends ComponentProps<"button"> {
7
+ fullWidth?: boolean
8
+ iconEnd?: ReactNode
9
+ iconOnly?: boolean
10
+ iconStart?: ReactNode
11
+ isLoading?: boolean
12
+ rounded?: boolean
13
+ size?: 32 | 36 | 40 | 48
14
+ variant?: "accent" | "ghost" | "primary" | "secondary"
15
+ }
16
+
17
+ type ButtonPropsInternal = ButtonPropsWithIcon | ButtonPropsWithText
18
+
19
+ interface ButtonPropsWithIcon extends ButtonProps {
20
+ children?: ReactNode
21
+ iconOnly: true
22
+ }
23
+
24
+ interface ButtonPropsWithText extends ButtonProps {
25
+ children?: string
26
+ iconOnly?: false
27
+ }
28
+
29
+ export function Button(props: ButtonPropsInternal): JSX.Element {
30
+ const {
31
+ children = "",
32
+ className: customClassName = "",
33
+ disabled = false,
34
+ fullWidth = false,
35
+ iconEnd = "",
36
+ iconOnly = false,
37
+ iconStart = "",
38
+ isLoading = false,
39
+ rounded = false,
40
+ size = 36,
41
+ type = "button",
42
+ variant = "primary",
43
+ ...rest
44
+ } = props
45
+
46
+ const combinedClassName = `
47
+ ${BUTTON_CLASS_NAME.BASE}
48
+ ${BUTTON_CLASS_NAME.SIZE[size]}
49
+ ${BUTTON_CLASS_NAME.VARIANT[variant.toUpperCase() as keyof typeof BUTTON_CLASS_NAME.VARIANT]}
50
+ ${fullWidth ? BUTTON_CLASS_NAME.WIDTH.FULL : BUTTON_CLASS_NAME.WIDTH.BASE}
51
+ ${iconOnly ? BUTTON_CLASS_NAME.ICON_ONLY : ""}
52
+ ${rounded ? BUTTON_CLASS_NAME.ROUNDED.FULL : BUTTON_CLASS_NAME.ROUNDED.BASE}
53
+ ${customClassName}
54
+ `
55
+ .replaceAll(/\s+/g, " ")
56
+ .trim()
57
+
58
+ return (
59
+ <button
60
+ className={combinedClassName}
61
+ disabled={disabled || isLoading}
62
+ type={type}
63
+ {...rest}
64
+ >
65
+ <ButtonBackground rounded={rounded} variant={variant} />
66
+ {isLoading && <ButtonSpinner />}
67
+ {!isLoading && iconStart}
68
+ {children}
69
+ {!isLoading && iconEnd}
70
+ </button>
71
+ )
72
+ }
73
+
74
+ export const BUTTON_CLASS_NAME = {
75
+ BASE: styles.button,
76
+ ICON_ONLY: styles.button__icon_only,
77
+ ROUNDED: {
78
+ BASE: styles.button__rounded_base,
79
+ FULL: styles.button__rounded_full,
80
+ },
81
+ SIZE: {
82
+ 32: styles.button__size_32,
83
+ 36: styles.button__size_36,
84
+ 40: styles.button__size_40,
85
+ 48: styles.button__size_48,
86
+ },
87
+ VARIANT: {
88
+ ACCENT: styles.button__variant_accent,
89
+ GHOST: styles.button__variant_ghost,
90
+ PRIMARY: styles.button__variant_primary,
91
+ SECONDARY: styles.button__variant_secondary,
92
+ },
93
+ WIDTH: {
94
+ BASE: styles.button__width_base,
95
+ FULL: styles.button__width_full,
96
+ },
97
+ } as const
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "@/button"
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "esModuleInterop": true,
5
+ "jsx": "react-jsx",
6
+ "lib": ["dom", "esnext"],
7
+ "module": "esnext",
8
+ "moduleResolution": "bundler",
9
+ "paths": {
10
+ "@/*": ["src/*"]
11
+ }
12
+ }
13
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "tsup"
2
+
3
+ export default defineConfig({
4
+ clean: true,
5
+ dts: true,
6
+ entry: ["src/index.ts"],
7
+ external: ["react", "react-dom"],
8
+ format: ["esm", "cjs"],
9
+ loader: { ".css": "copy" },
10
+ sourcemap: true,
11
+ splitting: true,
12
+ treeshake: true,
13
+ })