react-proportion-slider 0.9.0 → 0.9.2

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/package.json CHANGED
@@ -1,20 +1,35 @@
1
1
  {
2
2
  "name": "react-proportion-slider",
3
3
  "private": false,
4
- "version": "0.9.0",
4
+ "version": "0.9.2",
5
5
  "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/react-proportion-slider.umd.js",
10
+ "module": "./dist/react-proportion-slider.es.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/react-proportion-slider.es.js",
15
+ "require": "./dist/react-proportion-slider.umd.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
6
19
  "scripts": {
7
20
  "dev": "vite",
8
21
  "build": "tsc -b && vite build",
9
22
  "lint": "eslint .",
10
- "preview": "vite preview"
23
+ "preview": "vite preview",
24
+ "test": "vitest"
11
25
  },
12
26
  "dependencies": {
13
- "react": "^19.0.0",
14
- "react-dom": "^19.0.0"
27
+ "react": ">=16.8.0",
28
+ "react-dom": ">=16.8.0"
15
29
  },
16
30
  "devDependencies": {
17
31
  "@eslint/js": "^9.21.0",
32
+ "@testing-library/react": "^14.2.1",
18
33
  "@types/react": "^19.0.10",
19
34
  "@types/react-dom": "^19.0.4",
20
35
  "@vitejs/plugin-react": "^4.3.4",
@@ -22,8 +37,15 @@
22
37
  "eslint-plugin-react-hooks": "^5.1.0",
23
38
  "eslint-plugin-react-refresh": "^0.4.19",
24
39
  "globals": "^15.15.0",
40
+ "jsdom": "^24.0.0",
25
41
  "typescript": "~5.7.2",
26
42
  "typescript-eslint": "^8.24.1",
27
- "vite": "^6.2.0"
43
+ "vite": "^6.2.0",
44
+ "vite-plugin-dts": "^4.5.3",
45
+ "vitest": "^3.0.8"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=16.8.0",
49
+ "react-dom": ">=16.8.0"
28
50
  }
29
51
  }
package/dev/App.css DELETED
@@ -1,13 +0,0 @@
1
- html,body {
2
- height: 100%;
3
- }
4
-
5
- #root{
6
- height: 100%;
7
- background-color: #11110D;
8
- }
9
-
10
- * {
11
- font-family: Roboto, sans-serif;
12
- font-weight: 200;
13
- }
package/dev/App.tsx DELETED
@@ -1,49 +0,0 @@
1
- import React from "react";
2
- import "./App.css";
3
- import { ProportionSlider } from "../src/components/ProportionSlider";
4
-
5
- function App() {
6
- const [proportions, setProportions] = React.useState<[number, number]>([
7
- 50, 50,
8
- ]);
9
- return (
10
- <div
11
- style={{
12
- height: "100%",
13
- flex: 1,
14
- padding: "20px 200px",
15
- display: "flex",
16
- flexDirection: "column",
17
- justifyContent: "center",
18
- }}
19
- >
20
- <ProportionSlider
21
- value={proportions}
22
- proportions={[
23
- {
24
- name: "Skill",
25
- backgroundColor: "#31332E",
26
- },
27
- {
28
- name: "3.7 Sonnet",
29
- backgroundColor: "#5f625C",
30
- },
31
- ]}
32
- onChange={(change) => {
33
- setProportions(change);
34
- }}
35
- sliderOptions={{
36
- width: 5,
37
- gap: 5,
38
- backgroundColor: "#EC1308",
39
- }}
40
- options={{
41
- height: 50,
42
- displayValueType: "percentage",
43
- }}
44
- />
45
- </div>
46
- );
47
- }
48
-
49
- export default App;
package/dev/index.html DELETED
@@ -1,17 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8" />
6
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
- <title>Dev Environment</title>
9
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@200;400;700&display=swap" rel="stylesheet">
10
- </head>
11
-
12
- <body>
13
- <div id="root"></div>
14
- <script type="module" src="/dev/main.tsx"></script>
15
- </body>
16
-
17
- </html>
package/dev/main.tsx DELETED
@@ -1,9 +0,0 @@
1
- import React, { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
- import App from "./App.tsx";
4
-
5
- createRoot(document.getElementById("root")!).render(
6
- <StrictMode>
7
- <App />
8
- </StrictMode>
9
- );
package/eslint.config.js DELETED
@@ -1,28 +0,0 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import tseslint from 'typescript-eslint'
6
-
7
- export default tseslint.config(
8
- { ignores: ['dist'] },
9
- {
10
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
- files: ['**/*.{ts,tsx}'],
12
- languageOptions: {
13
- ecmaVersion: 2020,
14
- globals: globals.browser,
15
- },
16
- plugins: {
17
- 'react-hooks': reactHooks,
18
- 'react-refresh': reactRefresh,
19
- },
20
- rules: {
21
- ...reactHooks.configs.recommended.rules,
22
- 'react-refresh/only-export-components': [
23
- 'warn',
24
- { allowConstantExport: true },
25
- ],
26
- },
27
- },
28
- )
@@ -1,143 +0,0 @@
1
- import React, { useRef, useState, useMemo, useEffect } from "react";
2
-
3
- type DynamicChildPositionerProps = {
4
- rightNode: React.ReactNode;
5
- leftNode: React.ReactNode;
6
- options: {
7
- primary: "left" | "right";
8
- };
9
- backgroundColor?: string;
10
- width: number | string;
11
- };
12
-
13
- export const DynamicChildPositioner = ({
14
- rightNode,
15
- leftNode,
16
- options: { primary },
17
- width,
18
- backgroundColor = "gray",
19
- }: DynamicChildPositionerProps) => {
20
- const ref = useRef<HTMLDivElement | null>(null);
21
- const refRight = useRef<HTMLDivElement | null>(null);
22
- const refLeft = useRef<HTMLDivElement | null>(null);
23
- const [fitStatus, setFitStatus] = useState<"both" | "primary" | "none">(
24
- "both"
25
- );
26
-
27
- const rightStyle = useMemo(() => {
28
- const rightNoSpace =
29
- fitStatus === "none" || (fitStatus === "primary" && primary === "left");
30
- if (primary === "right") {
31
- return rightNoSpace ? STYLES["BOTTOM_RIGHT"] : STYLES["RIGHT"];
32
- } else {
33
- return rightNoSpace ? STYLES["TOP_LEFT"] : STYLES["RIGHT"];
34
- }
35
- }, [fitStatus, primary]);
36
-
37
- const leftStyle = useMemo(() => {
38
- const leftNoSpace =
39
- fitStatus === "none" || (fitStatus === "primary" && primary === "right");
40
- if (primary === "left") {
41
- return leftNoSpace ? STYLES["BOTTOM_LEFT"] : STYLES["LEFT"];
42
- } else {
43
- return leftNoSpace ? STYLES["TOP_RIGHT"] : STYLES["LEFT"];
44
- }
45
- }, [fitStatus, primary]);
46
-
47
- useEffect(() => {
48
- const interval = setInterval(() => {
49
- const textRight = refRight.current;
50
- const textLeft = refLeft.current;
51
- const container = ref.current;
52
- if (!container || !textRight || !textLeft) {
53
- return;
54
- }
55
- const containerRectWidth = container.getBoundingClientRect().width;
56
- const rightWidth = textRight.getBoundingClientRect().width;
57
- const leftWidth = textLeft.getBoundingClientRect().width;
58
-
59
- const { primaryWidth, secondaryWidth } =
60
- primary === "left"
61
- ? { primaryWidth: leftWidth, secondaryWidth: rightWidth }
62
- : { primaryWidth: rightWidth, secondaryWidth: leftWidth };
63
-
64
- const primaryCanFit = primaryWidth + 2 * GAP <= containerRectWidth;
65
- const secondaryCanFit =
66
- secondaryWidth + primaryWidth + 3 * GAP <= containerRectWidth;
67
-
68
- const fitStatus = primaryCanFit
69
- ? secondaryCanFit
70
- ? "both"
71
- : "primary"
72
- : "none";
73
-
74
- setFitStatus(fitStatus);
75
- }, 1000 / 30);
76
- return () => clearInterval(interval);
77
- }, [ref, refRight, refLeft, primary]);
78
-
79
- return (
80
- <div
81
- ref={ref}
82
- style={{
83
- position: "relative",
84
- width,
85
- backgroundColor,
86
- borderRadius: "5px",
87
- color: "white",
88
- }}
89
- >
90
- <div ref={refLeft} style={leftStyle}>
91
- {leftNode}
92
- </div>
93
- <div ref={refRight} style={rightStyle}>
94
- {rightNode}
95
- </div>
96
- </div>
97
- );
98
- };
99
-
100
- const GAP = 5;
101
- const COMMON_STYLES: React.CSSProperties = {
102
- position: "absolute",
103
- transition: "all 200ms cubic-bezier(.47,1.64,.41,.8)",
104
- transitionProperty: "transform, top, left, bottom, right",
105
- };
106
- const STYLES: Record<string, React.CSSProperties> = {
107
- TOP_LEFT: {
108
- ...COMMON_STYLES,
109
- left: GAP,
110
- top: -GAP,
111
- transform: "translateY(-100%)",
112
- },
113
- LEFT: {
114
- ...COMMON_STYLES,
115
- left: GAP,
116
- top: "50%",
117
- transform: "translateY(-50%)",
118
- },
119
- BOTTOM_LEFT: {
120
- ...COMMON_STYLES,
121
- left: GAP,
122
- bottom: -GAP,
123
- transform: "translateY(100%)",
124
- },
125
- TOP_RIGHT: {
126
- ...COMMON_STYLES,
127
- right: GAP,
128
- top: -GAP,
129
- transform: "translateY(-100%)",
130
- },
131
- RIGHT: {
132
- ...COMMON_STYLES,
133
- right: GAP,
134
- top: "50%",
135
- transform: "translateY(-50%)",
136
- },
137
- BOTTOM_RIGHT: {
138
- ...COMMON_STYLES,
139
- right: GAP,
140
- bottom: -GAP,
141
- transform: "translateY(100%)",
142
- },
143
- };
@@ -1,55 +0,0 @@
1
- import { DynamicChildPositioner } from "./DynamicChildPositioner";
2
- import { ProportionDetail, DisplayValueTypes } from "./ProportionSlider";
3
-
4
- export type ProportionProp = {
5
- value: number;
6
- total: number;
7
- detail: ProportionDetail;
8
- width: number | string;
9
- anchorName: "left" | "right";
10
- displayValueType: DisplayValueTypes;
11
- backgroundColor?: string;
12
- };
13
- export const Proportion = ({
14
- value,
15
- total,
16
- detail,
17
- width,
18
- anchorName,
19
- displayValueType,
20
- backgroundColor = "gray",
21
- }: ProportionProp) => {
22
- const nameNode = (
23
- <div
24
- style={{
25
- userSelect: "none",
26
- whiteSpace: "nowrap",
27
- }}
28
- >
29
- {detail.name}
30
- </div>
31
- );
32
- const percentNode =
33
- displayValueType === "percentage" ? (
34
- <div
35
- style={{
36
- userSelect: "none",
37
- whiteSpace: "nowrap",
38
- }}
39
- >
40
- {`${Math.round((value * 100) / total)}%`}
41
- </div>
42
- ) : null;
43
-
44
- return (
45
- <DynamicChildPositioner
46
- width={width}
47
- rightNode={anchorName === "right" ? nameNode : percentNode}
48
- leftNode={anchorName === "left" ? nameNode : percentNode}
49
- options={{
50
- primary: anchorName,
51
- }}
52
- backgroundColor={backgroundColor}
53
- ></DynamicChildPositioner>
54
- );
55
- };
@@ -1,112 +0,0 @@
1
- import { useCallback, useRef } from "react";
2
- import { SliderKnob } from "./SliderKnob";
3
- import { Proportion } from "./Proportion";
4
-
5
- export type ProportionDetail = {
6
- name: string;
7
- backgroundColor?: string;
8
- };
9
- export type DisplayValueTypes = "percentage" | "none";
10
-
11
- export type ProportionSliderProps = {
12
- value: [number, number];
13
- proportions: [ProportionDetail, ProportionDetail];
14
- onChange: (change: [number, number]) => void;
15
- sliderOptions?: {
16
- width: number;
17
- gap: number;
18
- backgroundColor?: string;
19
- };
20
- options?: {
21
- height: number;
22
- displayValueType: DisplayValueTypes;
23
- };
24
- };
25
- export const ProportionSlider = ({
26
- value,
27
- proportions,
28
- onChange,
29
- sliderOptions = {
30
- width: 5,
31
- gap: 2,
32
- },
33
- options = {
34
- height: 20,
35
- displayValueType: "percentage",
36
- },
37
- }: ProportionSliderProps) => {
38
- const refWidth = useRef<number | null>(null);
39
-
40
- const total = value[0] + value[1];
41
-
42
- const refStartX = useRef<number | null>(null);
43
- const refValue1Start = useRef<number | null>(null);
44
- const refValue2Start = useRef<number | null>(null);
45
- const sliderWidth = sliderOptions.width + sliderOptions.gap * 2;
46
- const onDragStart = useCallback(
47
- (px: number): void => {
48
- refStartX.current = px;
49
- refValue1Start.current = value[0];
50
- refValue2Start.current = value[1];
51
- },
52
- [value]
53
- );
54
- const onDragEnd = useCallback(() => {
55
- refStartX.current = null;
56
- refValue1Start.current = null;
57
- refValue2Start.current = null;
58
- }, []);
59
- const onDrag = useCallback(
60
- (px: number): void => {
61
- if (refStartX.current === null) return;
62
- const diffPx = px - refStartX.current;
63
- const totalWidthPx = refWidth.current! - sliderWidth;
64
- const total = refValue1Start.current! + refValue2Start.current!;
65
- let newValue1 = refValue1Start.current! + (diffPx / totalWidthPx) * total;
66
- newValue1 = Math.max(0, Math.min(total, newValue1));
67
- onChange([newValue1, total - newValue1]);
68
- },
69
- [onChange, sliderWidth]
70
- );
71
- return (
72
- <div
73
- ref={(el) => {
74
- if (el) {
75
- refWidth.current = el.getBoundingClientRect().width;
76
- }
77
- }}
78
- style={{
79
- display: "flex",
80
- flexDirection: "row",
81
- height: options.height,
82
- }}
83
- >
84
- <Proportion
85
- value={value[0]}
86
- backgroundColor={proportions[0].backgroundColor}
87
- total={total}
88
- width={`calc(${(value[0] * 100) / total}% - ${sliderWidth / 2}px)`}
89
- detail={proportions[0]}
90
- anchorName="left"
91
- displayValueType={options.displayValueType}
92
- />
93
- <SliderKnob
94
- backgroundColor={sliderOptions.backgroundColor}
95
- width={sliderOptions.width}
96
- gap={sliderOptions.gap}
97
- onDragStart={onDragStart}
98
- onDrag={onDrag}
99
- onDragEnd={onDragEnd}
100
- />
101
- <Proportion
102
- value={value[1]}
103
- total={total}
104
- backgroundColor={proportions[1].backgroundColor}
105
- width={`calc(${(value[1] * 100) / total}% - ${sliderWidth / 2}px)`}
106
- detail={proportions[1]}
107
- anchorName="right"
108
- displayValueType={options.displayValueType}
109
- />
110
- </div>
111
- );
112
- };
@@ -1,68 +0,0 @@
1
- import { useRef, useEffect } from "react";
2
-
3
- export type SliderKnobProps = {
4
- width: number;
5
- gap: number;
6
- backgroundColor?: string;
7
- onDragStart: (px: number) => void;
8
- onDrag: (px: number) => void;
9
- onDragEnd: () => void;
10
- };
11
-
12
- export const SliderKnob = ({
13
- width,
14
- gap,
15
- backgroundColor = "red",
16
- onDrag,
17
- onDragStart,
18
- onDragEnd,
19
- }: SliderKnobProps) => {
20
- const ref = useRef<HTMLDivElement | null>(null);
21
- const refIsDragging = useRef<boolean>(false);
22
- useEffect(() => {
23
- const onMouseDown = (e: MouseEvent) => {
24
- if (e.target !== ref.current) {
25
- return false;
26
- }
27
- refIsDragging.current = true;
28
- onDragStart(e.clientX);
29
- return true;
30
- };
31
- const onMouseMove = (e: MouseEvent) => {
32
- if (!refIsDragging.current) {
33
- return false;
34
- }
35
- onDrag(e.clientX);
36
- return true;
37
- };
38
- const onMouseUp = () => {
39
- if (!refIsDragging.current) {
40
- return false;
41
- }
42
- refIsDragging.current = false;
43
- onDragEnd();
44
- return true;
45
- };
46
- window.addEventListener("mousedown", onMouseDown);
47
- window.addEventListener("mousemove", onMouseMove);
48
- window.addEventListener("mouseup", onMouseUp);
49
- return () => {
50
- window.removeEventListener("mousedown", onMouseDown);
51
- window.removeEventListener("mousemove", onMouseMove);
52
- window.removeEventListener("mouseup", onMouseUp);
53
- };
54
- }, [onDragStart, onDrag, onDragEnd]);
55
- return (
56
- <div
57
- ref={ref}
58
- style={{
59
- width: `${width}px`,
60
- margin: `${gap}px ${gap}px`,
61
- alignSelf: "stretch",
62
- background: backgroundColor,
63
- borderRadius: "2px",
64
- cursor: "ew-resize",
65
- }}
66
- ></div>
67
- );
68
- };
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./components/ProportionSlider";
package/src/vite-env.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="vite/client" />
package/tsconfig.app.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "ES2020",
5
- "useDefineForClassFields": true,
6
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
- "module": "ESNext",
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "isolatedModules": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
- "jsx": "react-jsx",
17
-
18
- /* Linting */
19
- "strict": true,
20
- "noUnusedLocals": true,
21
- "noUnusedParameters": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
24
- },
25
- "include": ["src"]
26
- }
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "files": [],
3
- "references": [
4
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
6
- ]
7
- }
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "ES2022",
5
- "lib": ["ES2023"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
-
9
- /* Bundler mode */
10
- "moduleResolution": "bundler",
11
- "allowImportingTsExtensions": true,
12
- "isolatedModules": true,
13
- "moduleDetection": "force",
14
- "noEmit": true,
15
-
16
- /* Linting */
17
- "strict": true,
18
- "noUnusedLocals": true,
19
- "noUnusedParameters": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedSideEffectImports": true
22
- },
23
- "include": ["vite.config.ts"]
24
- }
package/vite.config.ts DELETED
@@ -1,26 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import react from "@vitejs/plugin-react";
3
-
4
- // https://vite.dev/config/
5
- export default defineConfig({
6
- plugins: [react()],
7
- build: {
8
- rollupOptions: {
9
- external: ["react", "react-dom"],
10
- output: {
11
- globals: {
12
- react: "React",
13
- "react-dom": "ReactDOM",
14
- },
15
- },
16
- },
17
- lib: {
18
- entry: "src/index.ts",
19
- name: "react-proportion-slider",
20
- fileName: (format) => `my-react-library.${format}.js`,
21
- },
22
- },
23
- server: {
24
- open: "/dev/index.html", // Open the dev environment on `npm run dev`
25
- },
26
- });