diff --git a/packages/number/.eslintrc.cjs b/packages/number/.eslintrc.cjs new file mode 100644 index 0000000..f63fe7d --- /dev/null +++ b/packages/number/.eslintrc.cjs @@ -0,0 +1,15 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + ], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh"], + rules: { + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + }, +}; diff --git a/packages/number/.gitignore b/packages/number/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/packages/number/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/number/README.md b/packages/number/README.md new file mode 100644 index 0000000..0d6babe --- /dev/null +++ b/packages/number/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/packages/number/lib/index.tsx b/packages/number/lib/index.tsx new file mode 100644 index 0000000..6afb550 --- /dev/null +++ b/packages/number/lib/index.tsx @@ -0,0 +1,193 @@ +"use client"; + +import type React from "react"; +import { forwardRef } from "react"; +import * as stylex from "@stylexjs/stylex"; +import { useState } from "react"; + +type ExtendProps = { extend?: stylex.StyleXStyles }; +export interface NumberProps extends React.InputHTMLAttributes { + label?: string, + description?: string, + error?: string + +} + +const styles = stylex.create({ + base: { + borderRadius: "0.25rem", + border: "0.0625rem solid var(--input-border-color, var(--border-color, #333333))", + + ":hover": { + border: "0.0625rem solid var(--input-hover-color, #B3B3B3)", + }, + + boxSizing: "border-box", + transitionProperty: + "color, background-color, border-color, text-decoration-color, fill, stroke", + transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)", + transitionDuration: "var(--transition-speed, 0.2s)", + boxShadow: "0 0.0625rem 0.125rem 0 rgba(0, 0, 0, 0.05)", + backgroundColor: "var(--background-color)", + }, + textInput: { + display: "inline", + + paddingTop: "0.25rem", + paddingBottom: "0.25rem", + paddingLeft: "0.75rem", + + outline: "none", + width: "calc(100% - 4.4rem - 0.125rem)", + + fontSize: "0.875rem", + lineHeight: "1.25rem", + color: "var(--input-text, var(--text-color, #FCFCFC))", + "::placeholder": { + color: "var(--input-text-placeholder, #666666)", + }, + backgroundColor: "transparent", + border: "none", + }, + + // TODO(clearfeld): add variants + success: { + border: "0.0625rem solid var(--input-success-color, #44cb69)", + }, + + error: { + border: "0.0625rem solid var(--input-error-color, #ff2e00)", + }, + + disabled: { + opacity: "0.75", + pointerEvents: "none", + }, + buttons: { + display: "inline" + }, + button: { + display: "inline", + ":hover": { + color: "0.0625rem solid var(--input-hover-color, #B3B3B3)", + backgroundColor: "var(--color-bg-compliment)", + border: "0.0625rem solid var(--color-bg-compliment)" + }, + ":focus": { + outline: "none", + boxShadow: "none", + }, + paddingTop: "0.25rem", + paddingBottom: "0.25rem", + width: "1.75rem", + height: "1.75rem", + border: "none", + backgroundColor: "var(--color-bg)", + color: "var(--text-color)", + }, + plusButton: { + marginRight: "0.125rem" + }, + minusButton: { + borderTopRightRadius: "0.25rem", + borderBottomRightRadius: "0.25rem" + }, + divider: { + backgroundColor: "var(--border-color, #333333)", + width: "0.0625rem", + display: "inline", + }, + focus: { + border: "0.0625rem solid var(--input-error-color, #00AAFF)", + }, + errorWrapper: { + margin: "auto", + display: "inline", + height: "2rem", + lineHeight: "2rem", + }, + errorMsg: { + paddingLeft: "0.5rem", + paddingTop: "0.0625rem", + fontSize: "0.875rem", + display: "inline", + }, +}); + +const NumberInput = forwardRef( + ({ extend, ...props }, ref) => { + const [numVal, setNumVal] = useState(0); + const [focus, setFocus] = useState(false); + const [focusPlus, setFocusPlus] = useState(false); + const [focusMinus, setFocusMinus] = useState(false); + + if (!props.error) { + props.error = ""; + } + + return ( +
+
+ { + const reg = /^\d+$/ + if (e.target.value.match(reg)) { + setNumVal(Number.parseInt(e.target.value)); + } + else if (e.target.value === "") { + setNumVal(0); + } + }} + onFocus={() => { + setFocus(true) + }} + onBlur={() => { + setFocus(false) + }} + /> + + +
+ {props.error !== "" && +
+ + Danger + + +
+ {props.error} +
+
} +
+ ); + }, +); +NumberInput.displayName = "Number"; + +export { NumberInput }; diff --git a/packages/number/package.json b/packages/number/package.json new file mode 100644 index 0000000..ed71bee --- /dev/null +++ b/packages/number/package.json @@ -0,0 +1,35 @@ +{ + "name": "@controlkit/number", + "version": "0.2.0", + "type": "module", + "module": "dist/index.js", + "types": "dist/lib/index.d.ts", + "files": ["dist"], + "scripts": { + "dev": "bunx --bun vite", + "build": "tsc -b && bunx --bun vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "bunx --bun vite preview" + }, + "peerDependencies": { + "@stylexjs/stylex": "^0.7.5", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "dependencies": {}, + "devDependencies": { + "@stylexjs/babel-plugin": "^0.7.5", + "@stylexjs/rollup-plugin": "^0.7.5", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.13.1", + "@typescript-eslint/parser": "^7.13.1", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "typescript": "^5.2.2", + "vite": "^5.3.1", + "vite-plugin-dts": "^3.9.1" + } +} diff --git a/packages/number/tsconfig.app.json b/packages/number/tsconfig.app.json new file mode 100644 index 0000000..185ccbb --- /dev/null +++ b/packages/number/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "skipLibCheck": true, + + "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + }, + "include": ["lib"], +} diff --git a/packages/number/tsconfig.json b/packages/number/tsconfig.json new file mode 100644 index 0000000..a5b06bf --- /dev/null +++ b/packages/number/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/packages/number/tsconfig.node.json b/packages/number/tsconfig.node.json new file mode 100644 index 0000000..476abae --- /dev/null +++ b/packages/number/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": true, + }, + "include": ["vite.config.ts"], +} diff --git a/packages/number/vite.config.ts b/packages/number/vite.config.ts new file mode 100644 index 0000000..e6cb8ed --- /dev/null +++ b/packages/number/vite.config.ts @@ -0,0 +1,44 @@ +import { resolve } from "path"; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +// import stylexPlugin from "@stylexjs/rollup-plugin"; +import dts from "vite-plugin-dts"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + dts({ + tsconfigPath: "./tsconfig.app.json", + }), + + react(), + ], + + esbuild: { + legalComments: "none", + }, + + build: { + target: "esnext", + + ssr: true, + + lib: { + entry: resolve(__dirname, "./lib/index.tsx"), + formats: ["es"], + }, + + rollupOptions: { + external: ["react", "react-dom", "@stylexjs/stylex"], + output: { + entryFileNames: "index.js", + + globals: { + react: "react", + reactDOM: "react-dom", + "@stylexjs/stylex": "@stylexjs/stylex", + }, + }, + }, + }, +});