From 205b85ffe2cb5f540f3894ec7e8dd45bcf08f6c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 06:32:53 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=E7=9F=A2=E5=8D=B0=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=82=92=E6=8F=8F=E7=94=BB=E3=81=99=E3=82=8Bcanvas?= =?UTF-8?q?=E3=83=84=E3=83=BC=E3=83=AB(/arrow-line)=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit クリックした点を順にline状に接続し、border-imageのように 矢印画像をセグメント方向へタイル状に繰り返し描画する。 添付の矢印画像は直接取得できないため、同じ意匠の仮アセット (src/asset/arrow-line/arrow-tile.svg)で代替している。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CpW56yMnmiTNUZg1RuHnKm --- .../web/src/asset/arrow-line/arrow-tile.svg | 10 ++ .../web/src/component/ArrowLineCanvas.tsx | 113 ++++++++++++++++++ packages/web/src/feature/arrow-line-canvas.ts | 66 ++++++++++ packages/web/src/feature/page-path.ts | 1 + packages/web/src/pages/arrow-line/index.tsx | 21 ++++ 5 files changed, 211 insertions(+) create mode 100644 packages/web/src/asset/arrow-line/arrow-tile.svg create mode 100644 packages/web/src/component/ArrowLineCanvas.tsx create mode 100644 packages/web/src/feature/arrow-line-canvas.ts create mode 100644 packages/web/src/pages/arrow-line/index.tsx diff --git a/packages/web/src/asset/arrow-line/arrow-tile.svg b/packages/web/src/asset/arrow-line/arrow-tile.svg new file mode 100644 index 0000000..8de39d5 --- /dev/null +++ b/packages/web/src/asset/arrow-line/arrow-tile.svg @@ -0,0 +1,10 @@ + + + diff --git a/packages/web/src/component/ArrowLineCanvas.tsx b/packages/web/src/component/ArrowLineCanvas.tsx new file mode 100644 index 0000000..5b4dde6 --- /dev/null +++ b/packages/web/src/component/ArrowLineCanvas.tsx @@ -0,0 +1,113 @@ +import styled from "@emotion/styled"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { percent, px } from "~/common/css-util"; +import { type Point, drawArrowLine } from "~/feature/arrow-line-canvas"; +import MockActionButton from "~/component/MockActionButton"; + +const CANVAS_WIDTH = 960; +const CANVAS_HEIGHT = 540; +const DRAW_OPTIONS = { + tileLength: 56, + tileThickness: 28, + pointRadius: 4, + pointColor: "#333333" +}; + +const Wrapper = styled.div({ + display: "flex", + flexDirection: "column", + gap: px(8) +}); + +const Toolbar = styled.div({ + display: "flex", + gap: px(8) +}); + +const StyledCanvas = styled.canvas({ + display: "block", + width: percent(100), + aspectRatio: `${CANVAS_WIDTH} / ${CANVAS_HEIGHT}`, + border: "1px solid #cccccc", + cursor: "crosshair", + touchAction: "none" +}); + +const getCanvasPoint = ( + canvas: HTMLCanvasElement, + event: { clientX: number; clientY: number } +): Point => { + const rect = canvas.getBoundingClientRect(); + return { + x: ((event.clientX - rect.left) / rect.width) * canvas.width, + y: ((event.clientY - rect.top) / rect.height) * canvas.height + }; +}; + +const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { + const canvasRef = useRef(null); + const imageRef = useRef(null); + const [points, setPoints] = useState([]); + const [isImageLoaded, setIsImageLoaded] = useState(false); + + useEffect(() => { + const image = new Image(); + image.onload = () => setIsImageLoaded(true); + image.src = arrowImageSrc; + imageRef.current = image; + return () => setIsImageLoaded(false); + }, [arrowImageSrc]); + + const redraw = useCallback(() => { + const canvas = canvasRef.current; + const image = imageRef.current; + const ctx = canvas?.getContext("2d"); + if (!canvas || !image || !ctx || !isImageLoaded) { + return; + } + drawArrowLine(ctx, image, points, DRAW_OPTIONS); + }, [points, isImageLoaded]); + + useEffect(() => { + redraw(); + }, [redraw]); + + const handleClick = (event: { clientX: number; clientY: number }) => { + const canvas = canvasRef.current; + if (!canvas) { + return; + } + setPoints(list => [...list, getCanvasPoint(canvas, event)]); + }; + + return ( + + + setPoints(list => list.slice(0, -1)) } + : null + } + > + ひとつ戻す + + setPoints([]) } : null + } + > + クリア + + + + + ); +}; + +export default ArrowLineCanvas; diff --git a/packages/web/src/feature/arrow-line-canvas.ts b/packages/web/src/feature/arrow-line-canvas.ts new file mode 100644 index 0000000..188107f --- /dev/null +++ b/packages/web/src/feature/arrow-line-canvas.ts @@ -0,0 +1,66 @@ +export type Point = { x: number; y: number }; + +export type ArrowLineOptions = { + tileLength: number; + tileThickness: number; + pointRadius: number; + pointColor: string; +}; + +const drawArrowSegment = ( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement, + from: Point, + to: Point, + { tileLength, tileThickness }: ArrowLineOptions +) => { + const dx = to.x - from.x; + const dy = to.y - from.y; + const length = Math.hypot(dx, dy); + if (!length) { + return; + } + const angle = Math.atan2(dy, dx); + const tileCount = Math.ceil(length / tileLength); + + ctx.save(); + ctx.translate(from.x, from.y); + ctx.rotate(angle); + ctx.beginPath(); + ctx.rect(0, -tileThickness / 2, length, tileThickness); + ctx.clip(); + for (let i = 0; i < tileCount; i += 1) { + ctx.drawImage( + image, + i * tileLength, + -tileThickness / 2, + tileLength, + tileThickness + ); + } + ctx.restore(); +}; + +const drawPoint = ( + ctx: CanvasRenderingContext2D, + point: Point, + { pointRadius, pointColor }: ArrowLineOptions +) => { + ctx.beginPath(); + ctx.arc(point.x, point.y, pointRadius, 0, Math.PI * 2); + ctx.fillStyle = pointColor; + ctx.fill(); +}; + +export const drawArrowLine = ( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement, + points: Point[], + options: ArrowLineOptions +) => { + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + points.slice(1).forEach((to, i) => { + drawArrowSegment(ctx, image, points[i], to, options); + }); + points.forEach(point => drawPoint(ctx, point, options)); +}; diff --git a/packages/web/src/feature/page-path.ts b/packages/web/src/feature/page-path.ts index 0530ab9..59b7de5 100644 --- a/packages/web/src/feature/page-path.ts +++ b/packages/web/src/feature/page-path.ts @@ -5,3 +5,4 @@ const PAGE_ROOT = new PageEntry(BASE_URL); export const PAGE_TOP = PAGE_ROOT; export const PAGE_ABOUT = PAGE_ROOT.child("about"); +export const PAGE_ARROW_LINE = PAGE_ROOT.child("arrow-line"); diff --git a/packages/web/src/pages/arrow-line/index.tsx b/packages/web/src/pages/arrow-line/index.tsx new file mode 100644 index 0000000..f62d546 --- /dev/null +++ b/packages/web/src/pages/arrow-line/index.tsx @@ -0,0 +1,21 @@ +import { makeSubPageMetadata } from "~/feature/defaultMetadata"; +import { PAGE_ARROW_LINE } from "~/feature/page-path"; +import MockStaticLayout from "~/component/MockStaticLayout"; +import PageMeta from "~/component/PageMeta"; +import ArrowLineCanvas from "~/component/ArrowLineCanvas"; +import ASSET_ARROW_TILE from "~/asset/arrow-line/arrow-tile.svg"; + +const metadata = makeSubPageMetadata({ + page: PAGE_ARROW_LINE, + subPageTitle: "Arrow Line" +}); + +const PageArrowLine = () => ( + + + + + +); + +export default PageArrowLine; From 87c619493caad5e993c68f9ffb0670a0c869bf74 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 06:47:28 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=E7=9F=A2=E5=8D=B0=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E7=9F=A2=E5=8D=B0=E5=AF=86=E5=BA=A6=E3=82=92?= =?UTF-8?q?=E5=8D=8A=E5=88=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tileLength を56→112にし、同じ長さの線上に描画される矢印の数を 半分にした。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CpW56yMnmiTNUZg1RuHnKm --- packages/web/src/component/ArrowLineCanvas.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/component/ArrowLineCanvas.tsx b/packages/web/src/component/ArrowLineCanvas.tsx index 5b4dde6..3d04fa4 100644 --- a/packages/web/src/component/ArrowLineCanvas.tsx +++ b/packages/web/src/component/ArrowLineCanvas.tsx @@ -7,7 +7,7 @@ import MockActionButton from "~/component/MockActionButton"; const CANVAS_WIDTH = 960; const CANVAS_HEIGHT = 540; const DRAW_OPTIONS = { - tileLength: 56, + tileLength: 112, tileThickness: 28, pointRadius: 4, pointColor: "#333333" From 887007cb9ce6052d98b8c0807c68534fe890c964 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 06:51:52 +0000 Subject: [PATCH 3/6] =?UTF-8?q?=E7=9F=A2=E5=8D=B0=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E9=96=93=E9=9A=94=E3=82=92UI=E3=81=A71?= =?UTF-8?q?=E3=80=9C5=E6=AE=B5=E9=9A=8E=E3=81=AB=E8=AA=BF=E6=95=B4?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 矢印画像は伸縮させずに固定サイズで描画し、タイル間の空白(間隔)を スライダーで変更できるようにした。以前のtileLengthを引き伸ばして 密度を下げる実装は矢印が変形してしまうため廃止。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CpW56yMnmiTNUZg1RuHnKm --- .../web/src/component/ArrowLineCanvas.tsx | 41 +++++++++++++++---- packages/web/src/feature/arrow-line-canvas.ts | 11 ++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/web/src/component/ArrowLineCanvas.tsx b/packages/web/src/component/ArrowLineCanvas.tsx index 3d04fa4..6d190be 100644 --- a/packages/web/src/component/ArrowLineCanvas.tsx +++ b/packages/web/src/component/ArrowLineCanvas.tsx @@ -1,17 +1,19 @@ import styled from "@emotion/styled"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { percent, px } from "~/common/css-util"; import { type Point, drawArrowLine } from "~/feature/arrow-line-canvas"; import MockActionButton from "~/component/MockActionButton"; +import { MockRangeFormRow } from "~/component/mock-form-ui"; const CANVAS_WIDTH = 960; const CANVAS_HEIGHT = 540; -const DRAW_OPTIONS = { - tileLength: 112, - tileThickness: 28, - pointRadius: 4, - pointColor: "#333333" -}; +const ARROW_WIDTH = 56; +const ARROW_THICKNESS = 28; +const SPACING_MIN = 1; +const SPACING_MAX = 5; +const SPACING_DEFAULT = 2; +const POINT_RADIUS = 4; +const POINT_COLOR = "#333333"; const Wrapper = styled.div({ display: "flex", @@ -49,6 +51,7 @@ const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { const imageRef = useRef(null); const [points, setPoints] = useState([]); const [isImageLoaded, setIsImageLoaded] = useState(false); + const [spacingLevel, setSpacingLevel] = useState(SPACING_DEFAULT); useEffect(() => { const image = new Image(); @@ -58,6 +61,17 @@ const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { return () => setIsImageLoaded(false); }, [arrowImageSrc]); + const drawOptions = useMemo( + () => ({ + arrowWidth: ARROW_WIDTH, + arrowStep: ARROW_WIDTH * spacingLevel, + tileThickness: ARROW_THICKNESS, + pointRadius: POINT_RADIUS, + pointColor: POINT_COLOR + }), + [spacingLevel] + ); + const redraw = useCallback(() => { const canvas = canvasRef.current; const image = imageRef.current; @@ -65,8 +79,8 @@ const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { if (!canvas || !image || !ctx || !isImageLoaded) { return; } - drawArrowLine(ctx, image, points, DRAW_OPTIONS); - }, [points, isImageLoaded]); + drawArrowLine(ctx, image, points, drawOptions); + }, [points, isImageLoaded, drawOptions]); useEffect(() => { redraw(); @@ -100,6 +114,15 @@ const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { クリア + { const dx = to.x - from.x; const dy = to.y - from.y; @@ -21,7 +22,7 @@ const drawArrowSegment = ( return; } const angle = Math.atan2(dy, dx); - const tileCount = Math.ceil(length / tileLength); + const tileCount = Math.ceil(length / arrowStep); ctx.save(); ctx.translate(from.x, from.y); @@ -32,9 +33,9 @@ const drawArrowSegment = ( for (let i = 0; i < tileCount; i += 1) { ctx.drawImage( image, - i * tileLength, + i * arrowStep, -tileThickness / 2, - tileLength, + arrowWidth, tileThickness ); } From 73eae45536e2f5b0e1c85f27ff4039fb578fdb88 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 06:56:54 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=E7=9F=A2=E5=8D=B0=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=83=99=E3=83=BC=E3=82=B9=E3=81=AB=E5=8D=8A?= =?UTF-8?q?=E9=80=8F=E6=98=8E=E3=81=AE=E5=A1=97=E3=82=8A=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 各セグメントの矢印タイルの下地として、アルファ50%の赤色で 帯状に塗りつぶすようにした。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CpW56yMnmiTNUZg1RuHnKm --- packages/web/src/component/ArrowLineCanvas.tsx | 5 ++++- packages/web/src/feature/arrow-line-canvas.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/web/src/component/ArrowLineCanvas.tsx b/packages/web/src/component/ArrowLineCanvas.tsx index 6d190be..0be845d 100644 --- a/packages/web/src/component/ArrowLineCanvas.tsx +++ b/packages/web/src/component/ArrowLineCanvas.tsx @@ -1,6 +1,6 @@ import styled from "@emotion/styled"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { percent, px } from "~/common/css-util"; +import { alphaColor, percent, px } from "~/common/css-util"; import { type Point, drawArrowLine } from "~/feature/arrow-line-canvas"; import MockActionButton from "~/component/MockActionButton"; import { MockRangeFormRow } from "~/component/mock-form-ui"; @@ -9,6 +9,8 @@ const CANVAS_WIDTH = 960; const CANVAS_HEIGHT = 540; const ARROW_WIDTH = 56; const ARROW_THICKNESS = 28; +const ARROW_COLOR = "#d1373b"; +const BASE_FILL_COLOR = alphaColor(ARROW_COLOR, 0.5); const SPACING_MIN = 1; const SPACING_MAX = 5; const SPACING_DEFAULT = 2; @@ -66,6 +68,7 @@ const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { arrowWidth: ARROW_WIDTH, arrowStep: ARROW_WIDTH * spacingLevel, tileThickness: ARROW_THICKNESS, + baseFillColor: BASE_FILL_COLOR, pointRadius: POINT_RADIUS, pointColor: POINT_COLOR }), diff --git a/packages/web/src/feature/arrow-line-canvas.ts b/packages/web/src/feature/arrow-line-canvas.ts index 9bd6639..a45fb04 100644 --- a/packages/web/src/feature/arrow-line-canvas.ts +++ b/packages/web/src/feature/arrow-line-canvas.ts @@ -4,6 +4,7 @@ export type ArrowLineOptions = { arrowWidth: number; arrowStep: number; tileThickness: number; + baseFillColor: string; pointRadius: number; pointColor: string; }; @@ -13,7 +14,7 @@ const drawArrowSegment = ( image: HTMLImageElement, from: Point, to: Point, - { arrowWidth, arrowStep, tileThickness }: ArrowLineOptions + { arrowWidth, arrowStep, tileThickness, baseFillColor }: ArrowLineOptions ) => { const dx = to.x - from.x; const dy = to.y - from.y; @@ -30,6 +31,8 @@ const drawArrowSegment = ( ctx.beginPath(); ctx.rect(0, -tileThickness / 2, length, tileThickness); ctx.clip(); + ctx.fillStyle = baseFillColor; + ctx.fillRect(0, -tileThickness / 2, length, tileThickness); for (let i = 0; i < tileCount; i += 1) { ctx.drawImage( image, From da9c1643c2842a45991a6f75eabab7c46b0b91a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 07:02:20 +0000 Subject: [PATCH 5/6] =?UTF-8?q?=E3=83=99=E3=83=BC=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E5=A1=97=E3=82=8A=E3=82=92=E3=82=BB=E3=82=B0=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E6=AF=8E=E3=81=AEfill=E3=81=8B=E3=82=89stroke?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=82=8B=E4=B8=80=E6=8B=AC=E6=8F=8F=E7=94=BB?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 各点を一本のパスでつなぎ、lineJoin: round のstrokeで下地を描画する ことで、セグメントの結合部分の汚れを解消した。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CpW56yMnmiTNUZg1RuHnKm --- packages/web/src/feature/arrow-line-canvas.ts | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/web/src/feature/arrow-line-canvas.ts b/packages/web/src/feature/arrow-line-canvas.ts index a45fb04..34d2e07 100644 --- a/packages/web/src/feature/arrow-line-canvas.ts +++ b/packages/web/src/feature/arrow-line-canvas.ts @@ -9,12 +9,36 @@ export type ArrowLineOptions = { pointColor: string; }; +const drawBaseLine = ( + ctx: CanvasRenderingContext2D, + points: Point[], + { tileThickness, baseFillColor }: ArrowLineOptions +) => { + if (points.length < 2) { + return; + } + ctx.save(); + ctx.beginPath(); + points.forEach((point, i) => { + if (i === 0) { + ctx.moveTo(point.x, point.y); + } else { + ctx.lineTo(point.x, point.y); + } + }); + ctx.lineWidth = tileThickness; + ctx.lineJoin = "round"; + ctx.strokeStyle = baseFillColor; + ctx.stroke(); + ctx.restore(); +}; + const drawArrowSegment = ( ctx: CanvasRenderingContext2D, image: HTMLImageElement, from: Point, to: Point, - { arrowWidth, arrowStep, tileThickness, baseFillColor }: ArrowLineOptions + { arrowWidth, arrowStep, tileThickness }: ArrowLineOptions ) => { const dx = to.x - from.x; const dy = to.y - from.y; @@ -31,8 +55,6 @@ const drawArrowSegment = ( ctx.beginPath(); ctx.rect(0, -tileThickness / 2, length, tileThickness); ctx.clip(); - ctx.fillStyle = baseFillColor; - ctx.fillRect(0, -tileThickness / 2, length, tileThickness); for (let i = 0; i < tileCount; i += 1) { ctx.drawImage( image, @@ -63,6 +85,7 @@ export const drawArrowLine = ( options: ArrowLineOptions ) => { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + drawBaseLine(ctx, points, options); points.slice(1).forEach((to, i) => { drawArrowSegment(ctx, image, points[i], to, options); }); From 44f834b30959455195cd3b701a30282c9b4a153f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 07:06:19 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=E7=9F=A2=E5=8D=B0=E3=81=AE=E9=96=93?= =?UTF-8?q?=E9=9A=94=E3=82=B9=E3=83=A9=E3=82=A4=E3=83=80=E3=83=BC=E3=82=92?= =?UTF-8?q?0.1=E5=88=BB=E3=81=BF=E3=81=A7=E8=AA=BF=E6=95=B4=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 見た目の微調整ができるよう、stepを1から0.1に変更した。 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CpW56yMnmiTNUZg1RuHnKm --- packages/web/src/component/ArrowLineCanvas.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web/src/component/ArrowLineCanvas.tsx b/packages/web/src/component/ArrowLineCanvas.tsx index 0be845d..ffe507b 100644 --- a/packages/web/src/component/ArrowLineCanvas.tsx +++ b/packages/web/src/component/ArrowLineCanvas.tsx @@ -13,6 +13,7 @@ const ARROW_COLOR = "#d1373b"; const BASE_FILL_COLOR = alphaColor(ARROW_COLOR, 0.5); const SPACING_MIN = 1; const SPACING_MAX = 5; +const SPACING_STEP = 0.1; const SPACING_DEFAULT = 2; const POINT_RADIUS = 4; const POINT_COLOR = "#333333"; @@ -123,7 +124,7 @@ const ArrowLineCanvas = ({ arrowImageSrc }: { arrowImageSrc: string }) => { onChange={setSpacingLevel} min={SPACING_MIN} max={SPACING_MAX} - step={1} + step={SPACING_STEP} error={null} />