@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.
package/dist/index.mjs ADDED
@@ -0,0 +1,115 @@
1
+ import styles from './button-background.module-FK4ABTRO.module.css';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import styles2 from './button-spinner.module-IRIJPNSF.module.css';
4
+ import styles3 from './button.module-QR72PPSM.module.css';
5
+
6
+ // src/button-background.tsx
7
+ function ButtonBackground(props) {
8
+ const { rounded = false, variant = "primary" } = props;
9
+ const combinedBackgroundStyles = `
10
+ ${BUTTON_BACKGROUND_CLASS_NAME.BASE}
11
+ ${rounded ? BUTTON_BACKGROUND_CLASS_NAME.ROUNDED.FULL : BUTTON_BACKGROUND_CLASS_NAME.ROUNDED.BASE}
12
+ ${variant === "accent" ? BUTTON_BACKGROUND_CLASS_NAME.VARIANT.ACCENT : ""}
13
+ ${variant === "primary" ? BUTTON_BACKGROUND_CLASS_NAME.VARIANT.PRIMARY : ""}
14
+ `.replaceAll(/\s+/g, " ").trim();
15
+ if (!(variant === "accent" || variant === "primary")) return /* @__PURE__ */ jsx(Fragment, {});
16
+ return /* @__PURE__ */ jsx(
17
+ "div",
18
+ {
19
+ className: combinedBackgroundStyles,
20
+ "data-element": "button-background"
21
+ }
22
+ );
23
+ }
24
+ var BUTTON_BACKGROUND_CLASS_NAME = {
25
+ BASE: styles.button_background,
26
+ ROUNDED: {
27
+ BASE: styles.button_background__rounded_base,
28
+ FULL: styles.button_background__rounded_full
29
+ },
30
+ VARIANT: {
31
+ ACCENT: styles.button_background__variant_accent,
32
+ PRIMARY: styles.button_background__variant_primary
33
+ }
34
+ };
35
+ function ButtonSpinner(props) {
36
+ const { size = 16 } = props;
37
+ return /* @__PURE__ */ jsx(
38
+ "div",
39
+ {
40
+ className: styles2.button_spinner,
41
+ style: { "--size": `${size}px` },
42
+ children: Array.from({ length: 12 }).map((_, index) => /* @__PURE__ */ jsx("div", {}, index))
43
+ }
44
+ );
45
+ }
46
+ function Button(props) {
47
+ const {
48
+ children = "",
49
+ className: customClassName = "",
50
+ disabled = false,
51
+ fullWidth = false,
52
+ iconEnd = "",
53
+ iconOnly = false,
54
+ iconStart = "",
55
+ isLoading = false,
56
+ rounded = false,
57
+ size = 36,
58
+ type = "button",
59
+ variant = "primary",
60
+ ...rest
61
+ } = props;
62
+ const combinedClassName = `
63
+ ${BUTTON_CLASS_NAME.BASE}
64
+ ${BUTTON_CLASS_NAME.SIZE[size]}
65
+ ${BUTTON_CLASS_NAME.VARIANT[variant.toUpperCase()]}
66
+ ${fullWidth ? BUTTON_CLASS_NAME.WIDTH.FULL : BUTTON_CLASS_NAME.WIDTH.BASE}
67
+ ${iconOnly ? BUTTON_CLASS_NAME.ICON_ONLY : ""}
68
+ ${rounded ? BUTTON_CLASS_NAME.ROUNDED.FULL : BUTTON_CLASS_NAME.ROUNDED.BASE}
69
+ ${customClassName}
70
+ `.replaceAll(/\s+/g, " ").trim();
71
+ return /* @__PURE__ */ jsxs(
72
+ "button",
73
+ {
74
+ className: combinedClassName,
75
+ disabled: disabled || isLoading,
76
+ type,
77
+ ...rest,
78
+ children: [
79
+ /* @__PURE__ */ jsx(ButtonBackground, { rounded, variant }),
80
+ isLoading && /* @__PURE__ */ jsx(ButtonSpinner, {}),
81
+ !isLoading && iconStart,
82
+ children,
83
+ !isLoading && iconEnd
84
+ ]
85
+ }
86
+ );
87
+ }
88
+ var BUTTON_CLASS_NAME = {
89
+ BASE: styles3.button,
90
+ ICON_ONLY: styles3.button__icon_only,
91
+ ROUNDED: {
92
+ BASE: styles3.button__rounded_base,
93
+ FULL: styles3.button__rounded_full
94
+ },
95
+ SIZE: {
96
+ 32: styles3.button__size_32,
97
+ 36: styles3.button__size_36,
98
+ 40: styles3.button__size_40,
99
+ 48: styles3.button__size_48
100
+ },
101
+ VARIANT: {
102
+ ACCENT: styles3.button__variant_accent,
103
+ GHOST: styles3.button__variant_ghost,
104
+ PRIMARY: styles3.button__variant_primary,
105
+ SECONDARY: styles3.button__variant_secondary
106
+ },
107
+ WIDTH: {
108
+ BASE: styles3.button__width_base,
109
+ FULL: styles3.button__width_full
110
+ }
111
+ };
112
+
113
+ export { BUTTON_CLASS_NAME, Button };
114
+ //# sourceMappingURL=index.mjs.map
115
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/button-background.tsx","../src/button-spinner.tsx","../src/button.tsx"],"names":["jsx","styles"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { JSX } from \"react\"\nimport type { ButtonProps } from \"@/button\"\nimport styles from \"@/button-background.module.css\"\n\nexport interface ButtonBackgroundProps\n extends Pick<ButtonProps, \"rounded\" | \"variant\"> {}\n\nexport function ButtonBackground(props: ButtonBackgroundProps): JSX.Element {\n const { rounded = false, variant = \"primary\" } = props\n\n const combinedBackgroundStyles = `\n ${BUTTON_BACKGROUND_CLASS_NAME.BASE}\n ${rounded ? BUTTON_BACKGROUND_CLASS_NAME.ROUNDED.FULL : BUTTON_BACKGROUND_CLASS_NAME.ROUNDED.BASE}\n ${variant === \"accent\" ? BUTTON_BACKGROUND_CLASS_NAME.VARIANT.ACCENT : \"\"}\n ${variant === \"primary\" ? BUTTON_BACKGROUND_CLASS_NAME.VARIANT.PRIMARY : \"\"}\n `\n .replaceAll(/\\s+/g, \" \")\n .trim()\n\n if (!(variant === \"accent\" || variant === \"primary\")) return <></>\n\n return (\n <div\n className={combinedBackgroundStyles}\n data-element=\"button-background\"\n />\n )\n}\n\nexport const BUTTON_BACKGROUND_CLASS_NAME = {\n BASE: styles.button_background,\n ROUNDED: {\n BASE: styles.button_background__rounded_base,\n FULL: styles.button_background__rounded_full,\n },\n VARIANT: {\n ACCENT: styles.button_background__variant_accent,\n PRIMARY: styles.button_background__variant_primary,\n },\n} as const\n","import type { CSSProperties, JSX } from \"react\"\nimport styles from \"@/button-spinner.module.css\"\n\nexport interface ButtonSpinnerProps {\n size?: number\n}\n\nexport function ButtonSpinner(props: ButtonSpinnerProps): JSX.Element {\n const { size = 16 } = props\n\n return (\n <div\n className={styles.button_spinner}\n style={{ \"--size\": `${size}px` } as CSSProperties}\n >\n {Array.from({ length: 12 }).map((_, index) => (\n <div key={index} />\n ))}\n </div>\n )\n}\n","import type { ComponentProps, JSX, ReactNode } from \"react\"\nimport { ButtonBackground } from \"@/button-background\"\nimport { ButtonSpinner } from \"@/button-spinner\"\nimport styles from \"@/button.module.css\"\n\nexport interface ButtonProps extends ComponentProps<\"button\"> {\n fullWidth?: boolean\n iconEnd?: ReactNode\n iconOnly?: boolean\n iconStart?: ReactNode\n isLoading?: boolean\n rounded?: boolean\n size?: 32 | 36 | 40 | 48\n variant?: \"accent\" | \"ghost\" | \"primary\" | \"secondary\"\n}\n\ntype ButtonPropsInternal = ButtonPropsWithIcon | ButtonPropsWithText\n\ninterface ButtonPropsWithIcon extends ButtonProps {\n children?: ReactNode\n iconOnly: true\n}\n\ninterface ButtonPropsWithText extends ButtonProps {\n children?: string\n iconOnly?: false\n}\n\nexport function Button(props: ButtonPropsInternal): JSX.Element {\n const {\n children = \"\",\n className: customClassName = \"\",\n disabled = false,\n fullWidth = false,\n iconEnd = \"\",\n iconOnly = false,\n iconStart = \"\",\n isLoading = false,\n rounded = false,\n size = 36,\n type = \"button\",\n variant = \"primary\",\n ...rest\n } = props\n\n const combinedClassName = `\n ${BUTTON_CLASS_NAME.BASE}\n ${BUTTON_CLASS_NAME.SIZE[size]}\n ${BUTTON_CLASS_NAME.VARIANT[variant.toUpperCase() as keyof typeof BUTTON_CLASS_NAME.VARIANT]}\n ${fullWidth ? BUTTON_CLASS_NAME.WIDTH.FULL : BUTTON_CLASS_NAME.WIDTH.BASE}\n ${iconOnly ? BUTTON_CLASS_NAME.ICON_ONLY : \"\"}\n ${rounded ? BUTTON_CLASS_NAME.ROUNDED.FULL : BUTTON_CLASS_NAME.ROUNDED.BASE}\n ${customClassName}\n `\n .replaceAll(/\\s+/g, \" \")\n .trim()\n\n return (\n <button\n className={combinedClassName}\n disabled={disabled || isLoading}\n type={type}\n {...rest}\n >\n <ButtonBackground rounded={rounded} variant={variant} />\n {isLoading && <ButtonSpinner />}\n {!isLoading && iconStart}\n {children}\n {!isLoading && iconEnd}\n </button>\n )\n}\n\nexport const BUTTON_CLASS_NAME = {\n BASE: styles.button,\n ICON_ONLY: styles.button__icon_only,\n ROUNDED: {\n BASE: styles.button__rounded_base,\n FULL: styles.button__rounded_full,\n },\n SIZE: {\n 32: styles.button__size_32,\n 36: styles.button__size_36,\n 40: styles.button__size_40,\n 48: styles.button__size_48,\n },\n VARIANT: {\n ACCENT: styles.button__variant_accent,\n GHOST: styles.button__variant_ghost,\n PRIMARY: styles.button__variant_primary,\n SECONDARY: styles.button__variant_secondary,\n },\n WIDTH: {\n BASE: styles.button__width_base,\n FULL: styles.button__width_full,\n },\n} as const\n"]}
@@ -0,0 +1,96 @@
1
+ import css from "@eslint/css"
2
+ import js from "@eslint/js"
3
+ import pluginPerfectionist from "eslint-plugin-perfectionist"
4
+ import pluginReact from "eslint-plugin-react"
5
+ import pluginUnicorn from "eslint-plugin-unicorn"
6
+ import pluginUnusedImports from "eslint-plugin-unused-imports"
7
+ import { defineConfig } from "eslint/config"
8
+ import globals from "globals"
9
+ import tseslint from "typescript-eslint"
10
+
11
+ export default defineConfig([
12
+ // Global ignores
13
+ {
14
+ ignores: ["dist/"],
15
+ },
16
+ // Base configurations for JS/TS files only
17
+ {
18
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
19
+ ...pluginPerfectionist.configs["recommended-natural"],
20
+ },
21
+ {
22
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
23
+ ...pluginReact.configs.flat.recommended,
24
+ settings: {
25
+ react: {
26
+ version: "detect",
27
+ },
28
+ },
29
+ },
30
+ {
31
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
32
+ ...pluginUnicorn.configs.all,
33
+ },
34
+ ...tseslint.configs.recommended.map((config) => ({
35
+ ...config,
36
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
37
+ })),
38
+ {
39
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
40
+ languageOptions: { globals: globals.browser },
41
+ },
42
+ // CSS configuration
43
+ {
44
+ extends: ["css/recommended"],
45
+ files: ["**/*.css"],
46
+ language: "css/css",
47
+ plugins: { css },
48
+ rules: {
49
+ "css/use-baseline": "off",
50
+ },
51
+ },
52
+ // JavaScript configuration
53
+ {
54
+ extends: ["js/recommended"],
55
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
56
+ plugins: { js },
57
+ },
58
+ // Custom rules for JS/TS files only
59
+ {
60
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
61
+ plugins: {
62
+ "unused-imports": pluginUnusedImports,
63
+ },
64
+ rules: {
65
+ "@typescript-eslint/no-empty-object-type": [
66
+ "error",
67
+ {
68
+ allowInterfaces: "with-single-extends",
69
+ },
70
+ ],
71
+ "perfectionist/sort-imports": [
72
+ "error",
73
+ {
74
+ newlinesBetween: "never",
75
+ sortSideEffects: true,
76
+ },
77
+ ],
78
+ "react/react-in-jsx-scope": "off",
79
+ "unicorn/consistent-function-scoping": "off",
80
+ "unicorn/import-style": "off",
81
+ "unicorn/no-keyword-prefix": "off",
82
+ "unicorn/no-unused-properties": "warn",
83
+ "unicorn/prevent-abbreviations": "off",
84
+ "unused-imports/no-unused-imports": "error",
85
+ "unused-imports/no-unused-vars": [
86
+ "warn",
87
+ {
88
+ args: "after-used",
89
+ argsIgnorePattern: "^_",
90
+ vars: "all",
91
+ varsIgnorePattern: "^_",
92
+ },
93
+ ],
94
+ },
95
+ },
96
+ ])
package/global.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module "*.module.css" {
2
+ const classes: { [key: string]: string }
3
+ export default classes
4
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@nattui/react-components",
3
+ "version": "0.0.1",
4
+ "description": "A collection of reusable React components built with TypeScript and CSS Modules",
5
+ "keywords": [
6
+ "components",
7
+ "react"
8
+ ],
9
+ "homepage": "https://github.com/nattui/react-components#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/nattui/react-components/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/nattui/react-components.git"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Natt Nguyen <hello@natt.sh> (https://natt.sh)",
19
+ "exports": "./dist/index.js",
20
+ "main": "./dist/index.js",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "devDependencies": {
24
+ "@eslint/css": "^0.9.0",
25
+ "@eslint/js": "^9.29.0",
26
+ "@types/react": "^19.1.8",
27
+ "eslint": "^9.29.0",
28
+ "eslint-plugin-perfectionist": "^4.14.0",
29
+ "eslint-plugin-react": "^7.37.5",
30
+ "eslint-plugin-unicorn": "^59.0.1",
31
+ "eslint-plugin-unused-imports": "^4.1.4",
32
+ "globals": "^16.2.0",
33
+ "prettier": "^3.5.3",
34
+ "prettier-plugin-css-order": "^2.1.2",
35
+ "prettier-plugin-packagejson": "^2.5.15",
36
+ "prettier-plugin-sort-json": "^4.1.1",
37
+ "tsup": "^8.5.0",
38
+ "typescript": "^5.8.3",
39
+ "typescript-eslint": "^8.34.0"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^18.0.0 || ^19.0.0",
43
+ "react-dom": "^18.0.0 || ^19.0.0"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "check": "pnpm check:format && pnpm check:lint",
51
+ "check:fix": "pnpm check:format:fix && pnpm check:lint:fix",
52
+ "check:format": "prettier --check .",
53
+ "check:format:fix": "prettier --write .",
54
+ "check:lint": "eslint .",
55
+ "check:lint:fix": "eslint --fix .",
56
+ "dev": "tsup --watch",
57
+ "update": "pnpx npm-check-updates --upgrade"
58
+ }
59
+ }
@@ -0,0 +1,38 @@
1
+ /* Base */
2
+ .button_background {
3
+ position: absolute;
4
+ top: 0;
5
+ right: 0;
6
+ bottom: 50%;
7
+ left: 0;
8
+ opacity: 0.2;
9
+ z-index: -1;
10
+ filter: none !important;
11
+ transition: opacity 150ms;
12
+ }
13
+
14
+ /* Rounded */
15
+ .button_background__rounded_base {
16
+ border-radius: 7px;
17
+ }
18
+
19
+ .button_background__rounded_full {
20
+ border-radius: 9999px;
21
+ }
22
+
23
+ /* Variant */
24
+ .button_background__variant_accent {
25
+ background: linear-gradient(
26
+ to bottom,
27
+ var(--color-primary-7, #edadc8),
28
+ var(--color-primary-8, #e58fb1)
29
+ );
30
+ }
31
+
32
+ .button_background__variant_primary {
33
+ background: linear-gradient(
34
+ to bottom,
35
+ var(--color-gray-10, #86848d),
36
+ var(--color-gray-11, #6f6e77)
37
+ );
38
+ }
@@ -0,0 +1,40 @@
1
+ import type { JSX } from "react"
2
+ import type { ButtonProps } from "@/button"
3
+ import styles from "@/button-background.module.css"
4
+
5
+ export interface ButtonBackgroundProps
6
+ extends Pick<ButtonProps, "rounded" | "variant"> {}
7
+
8
+ export function ButtonBackground(props: ButtonBackgroundProps): JSX.Element {
9
+ const { rounded = false, variant = "primary" } = props
10
+
11
+ const combinedBackgroundStyles = `
12
+ ${BUTTON_BACKGROUND_CLASS_NAME.BASE}
13
+ ${rounded ? BUTTON_BACKGROUND_CLASS_NAME.ROUNDED.FULL : BUTTON_BACKGROUND_CLASS_NAME.ROUNDED.BASE}
14
+ ${variant === "accent" ? BUTTON_BACKGROUND_CLASS_NAME.VARIANT.ACCENT : ""}
15
+ ${variant === "primary" ? BUTTON_BACKGROUND_CLASS_NAME.VARIANT.PRIMARY : ""}
16
+ `
17
+ .replaceAll(/\s+/g, " ")
18
+ .trim()
19
+
20
+ if (!(variant === "accent" || variant === "primary")) return <></>
21
+
22
+ return (
23
+ <div
24
+ className={combinedBackgroundStyles}
25
+ data-element="button-background"
26
+ />
27
+ )
28
+ }
29
+
30
+ export const BUTTON_BACKGROUND_CLASS_NAME = {
31
+ BASE: styles.button_background,
32
+ ROUNDED: {
33
+ BASE: styles.button_background__rounded_base,
34
+ FULL: styles.button_background__rounded_full,
35
+ },
36
+ VARIANT: {
37
+ ACCENT: styles.button_background__variant_accent,
38
+ PRIMARY: styles.button_background__variant_primary,
39
+ },
40
+ } as const
@@ -0,0 +1,95 @@
1
+ .button_spinner {
2
+ position: relative;
3
+ scale: -1 1;
4
+ animation: spinner 1000ms steps(12, end) infinite;
5
+ width: var(--size);
6
+ height: var(--size);
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
+ transform-origin: center left;
20
+ translate: 0 -50%;
21
+ transition: all 150ms;
22
+ width: calc(var(--size) / 2);
23
+ height: calc(var(--size) / 12);
24
+ pointer-events: none;
25
+ }
26
+
27
+ .button_spinner > div::after {
28
+ position: absolute;
29
+ right: 0;
30
+ border-radius: 9999px;
31
+ background-color: currentColor;
32
+ width: 50%;
33
+ height: 100%;
34
+ content: "";
35
+ }
36
+
37
+ .button_spinner > div:nth-child(1) {
38
+ rotate: 0deg;
39
+ opacity: 1;
40
+ }
41
+
42
+ .button_spinner > div:nth-child(2) {
43
+ rotate: 30deg;
44
+ opacity: 0.925;
45
+ }
46
+
47
+ .button_spinner > div:nth-child(3) {
48
+ rotate: 60deg;
49
+ opacity: 0.85;
50
+ }
51
+
52
+ .button_spinner > div:nth-child(4) {
53
+ rotate: 90deg;
54
+ opacity: 0.775;
55
+ }
56
+
57
+ .button_spinner > div:nth-child(5) {
58
+ rotate: 120deg;
59
+ opacity: 0.7;
60
+ }
61
+
62
+ .button_spinner > div:nth-child(6) {
63
+ rotate: 150deg;
64
+ opacity: 0.625;
65
+ }
66
+
67
+ .button_spinner > div:nth-child(7) {
68
+ rotate: 180deg;
69
+ opacity: 0.55;
70
+ }
71
+
72
+ .button_spinner > div:nth-child(8) {
73
+ rotate: 210deg;
74
+ opacity: 0.475;
75
+ }
76
+
77
+ .button_spinner > div:nth-child(9) {
78
+ rotate: 240deg;
79
+ opacity: 0.4;
80
+ }
81
+
82
+ .button_spinner > div:nth-child(10) {
83
+ rotate: 270deg;
84
+ opacity: 0.325;
85
+ }
86
+
87
+ .button_spinner > div:nth-child(11) {
88
+ rotate: 300deg;
89
+ opacity: 0.25;
90
+ }
91
+
92
+ .button_spinner > div:nth-child(12) {
93
+ rotate: 330deg;
94
+ opacity: 0.15;
95
+ }
@@ -0,0 +1,21 @@
1
+ import type { CSSProperties, JSX } from "react"
2
+ import styles from "@/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
13
+ className={styles.button_spinner}
14
+ style={{ "--size": `${size}px` } as CSSProperties}
15
+ >
16
+ {Array.from({ length: 12 }).map((_, index) => (
17
+ <div key={index} />
18
+ ))}
19
+ </div>
20
+ )
21
+ }