From fcbc47f9ef21c08efd69bd40a9c19725ae4eb59c Mon Sep 17 00:00:00 2001
From: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
Date: Sat, 11 Apr 2026 18:46:44 +0530
Subject: [PATCH 1/5] feat(ux): display polygon coordinates in modal on shape
completion
When a user finishes designing a polygon (via Close Shape, double-click,
Enter, or Esc), the normalized SVG coordinates are now displayed in a
focused modal overlay on the canvas instead of being shown in the textbox
below where they can easily be missed.
Modal features:
- Inline copy button with 'Copied' feedback
- 'Done' action to dismiss and keep shape
- 'Clear & Start Over' action to reset canvas
- Overlay click-to-close for quick dismissal
- 'View Coordinates' button to re-open modal after close
- Full keyboard and screen-reader accessibility
- Dark/light theme support
The fallback textarea is retained for accessibility and non-JS contexts.
Fixes #148
Signed-off-by: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
---
.../ShapeBuilder/CoordinatesModal.js | 238 ++++++++++++++++++
site/src/components/ShapeBuilder/index.js | 78 +++---
site/src/pages/index.js | 2 +-
3 files changed, 282 insertions(+), 36 deletions(-)
create mode 100644 site/src/components/ShapeBuilder/CoordinatesModal.js
diff --git a/site/src/components/ShapeBuilder/CoordinatesModal.js b/site/src/components/ShapeBuilder/CoordinatesModal.js
new file mode 100644
index 0000000..33aa620
--- /dev/null
+++ b/site/src/components/ShapeBuilder/CoordinatesModal.js
@@ -0,0 +1,238 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import { CopyIcon } from "@sistent/sistent";
+
+// ─── Overlay ────────────────────────────────────────────────────────────────
+const Overlay = styled.div`
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.65);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1300;
+ /* subtle entrance */
+ animation: fadeIn 0.18s ease;
+
+ @keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+`;
+
+// ─── Modal Card ──────────────────────────────────────────────────────────────
+const ModalCard = styled.div`
+ background: ${({ theme }) => theme.body || "#1e2227"};
+ border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
+ border-radius: 12px;
+ padding: 2rem;
+ width: min(560px, 92vw);
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5);
+ animation: slideUp 0.2s ease;
+ position: relative;
+
+ @keyframes slideUp {
+ from { transform: translateY(24px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+ }
+`;
+
+// ─── Header row ─────────────────────────────────────────────────────────────
+const Header = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.25rem;
+`;
+
+const Title = styled.h2`
+ font-size: 1.15rem;
+ font-weight: 600;
+ color: ${({ theme }) => theme.text || "#ffffff"};
+ margin: 0;
+`;
+
+const CloseButton = styled.button`
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: ${({ theme }) => theme.text || "#ffffff"};
+ font-size: 1.4rem;
+ line-height: 1;
+ padding: 2px 6px;
+ border-radius: 4px;
+ opacity: 0.7;
+ transition: opacity 0.15s;
+
+ &:hover { opacity: 1; }
+`;
+
+// ─── Sub-label ───────────────────────────────────────────────────────────────
+const SubLabel = styled.p`
+ font-size: 0.8rem;
+ color: ${({ theme }) => theme.textMuted || "#8b949e"};
+ margin: 0 0 1rem 0;
+`;
+
+// ─── Code block ──────────────────────────────────────────────────────────────
+const CodeBlock = styled.div`
+ position: relative;
+ background: ${({ theme }) => theme.codeBackground || "#0d1117"};
+ border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
+ border-radius: 8px;
+ padding: 1rem 3rem 1rem 1rem;
+ margin-bottom: 1.25rem;
+`;
+
+const CoordText = styled.pre`
+ color: #00b39f;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+ font-size: 0.85rem;
+ line-height: 1.6;
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-all;
+`;
+
+const InlineCopyButton = styled.button`
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background: none;
+ border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
+ border-radius: 6px;
+ cursor: pointer;
+ padding: 5px 8px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 0.72rem;
+ color: ${({ theme }) => theme.text || "#ffffff"};
+ opacity: 0.75;
+ transition: opacity 0.15s, background 0.15s;
+
+ &:hover {
+ opacity: 1;
+ background: ${({ theme }) => theme.border || "#2d3139"};
+ }
+
+ svg {
+ width: 14px;
+ height: 14px;
+ fill: currentColor;
+ }
+`;
+
+// ─── Action row ──────────────────────────────────────────────────────────────
+const ActionRow = styled.div`
+ display: flex;
+ gap: 0.75rem;
+ justify-content: flex-end;
+ flex-wrap: wrap;
+`;
+
+const ActionButton = styled.button`
+ padding: 0.55rem 1.3rem;
+ border-radius: 8px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.15s, opacity 0.15s;
+
+ &.primary {
+ background: #00b39f;
+ color: #fff;
+ border: none;
+ &:hover { background: #009684; }
+ }
+
+ &.secondary {
+ background: transparent;
+ color: ${({ theme }) => theme.text || "#ffffff"};
+ border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
+ &:hover { opacity: 0.75; }
+ }
+`;
+
+// ─── Component ───────────────────────────────────────────────────────────────
+const CoordinatesModal = ({ coordinates, onClose, onClear, theme }) => {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(coordinates);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error("Failed to copy:", err);
+ }
+ };
+
+ // Close on overlay click (not on card click)
+ const handleOverlayClick = (e) => {
+ if (e.target === e.currentTarget) onClose();
+ };
+
+ return (
+
+
+ {/* ── Header ── */}
+
+
+ Polygon Coordinates
+
+
+ ×
+
+
+
+
+ SVG-format values normalized to the [−1, 1] range. Copy and paste
+ into your Meshery Component's
+ polygon shape field.
+
+
+ {/* ── Code block ── */}
+
+ {coordinates}
+
+ {copied ? (
+ "✓ Copied"
+ ) : (
+ <>
+
+ Copy
+ >
+ )}
+
+
+
+ {/* ── Actions ── */}
+
+
+ Clear & Start Over
+
+
+ Done
+
+
+
+
+ );
+};
+
+export default CoordinatesModal;
diff --git a/site/src/components/ShapeBuilder/index.js b/site/src/components/ShapeBuilder/index.js
index 5ab1d15..0f7b455 100644
--- a/site/src/components/ShapeBuilder/index.js
+++ b/site/src/components/ShapeBuilder/index.js
@@ -4,6 +4,7 @@ import { Wrapper, CanvasContainer, OutputBox, StyledSVG, CopyButton } from "./sh
import { Button, Typography, Box, CopyIcon, Select, MenuItem, Slider, FormControl } from "@sistent/sistent";
import { SVG, extend as SVGextend } from "@svgdotjs/svg.js";
import draw from "@svgdotjs/svg.draw.js";
+import CoordinatesModal from "./CoordinatesModal";
SVGextend(SVG.Polygon, draw);
@@ -11,7 +12,7 @@ const SCALE_PRESETS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3];
const MIN_SCALE = 0.1;
const MAX_SCALE = 3;
-const ShapeBuilder = () => {
+const ShapeBuilder = ({ theme }) => {
const boardRef = useRef(null);
const polyRef = useRef(null);
const keyHandlersRef = useRef({});
@@ -22,9 +23,11 @@ const ShapeBuilder = () => {
const [scale, setScale] = useState(1);
const [currentPreset, setCurrentPreset] = useState(1);
+ // ── Modal state ─────────────────────────────────────────────────────────────
+ const [modalOpen, setModalOpen] = useState(false);
+
const handleCopyToClipboard = async () => {
if (!result.trim()) return;
-
try {
await navigator.clipboard.writeText(result);
setShowCopied(true);
@@ -44,17 +47,17 @@ const ShapeBuilder = () => {
const showCytoArray = () => {
const poly = polyRef.current;
if (!poly) return;
-
try {
const points = getPlottedPoints(poly);
if (!points) throw new Error("Invalid or empty polygon points");
-
const normalized = points
.map(([x, y]) => [(x - 260) / 260, (y - 260) / 260])
.flat()
.join(" ");
setResult(normalized);
setError(null);
+ // ── Open the modal whenever coordinates are ready ──
+ setModalOpen(true);
} catch (err) {
setError("Failed to extract and normalize polygon points.");
console.error("showCytoArray error:", err);
@@ -64,27 +67,21 @@ const ShapeBuilder = () => {
const applyScale = (newScale) => {
const poly = polyRef.current;
if (!poly) return;
-
const points = getPlottedPoints(poly);
if (!points || points.length === 0) return;
-
if (!basePointsRef.current) {
basePointsRef.current = points;
}
-
const basePoints = basePointsRef.current;
-
const xs = basePoints.map(p => p[0]);
const ys = basePoints.map(p => p[1]);
const centerX = (Math.max(...xs) + Math.min(...xs)) / 2;
const centerY = (Math.max(...ys) + Math.min(...ys)) / 2;
-
const scaledPoints = basePoints.map(([x, y]) => {
const dx = x - centerX;
const dy = y - centerY;
return [centerX + dx * newScale, centerY + dy * newScale];
});
-
poly.plot(scaledPoints);
showCytoArray();
};
@@ -92,10 +89,8 @@ const ShapeBuilder = () => {
const handleScaleChange = (newScale) => {
const clampedScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale));
setScale(clampedScale);
-
const matchingPreset = SCALE_PRESETS.find(p => Math.abs(p - clampedScale) < 0.01);
setCurrentPreset(matchingPreset || clampedScale);
-
applyScale(clampedScale);
};
@@ -113,17 +108,14 @@ const ShapeBuilder = () => {
const handleKeyDown = (e) => {
const poly = polyRef.current;
if (!poly) return;
-
if (e.ctrlKey) {
poly.draw("param", "snapToGrid", 0.001);
}
-
if (e.key === "Enter" || e.key === "Escape") {
poly.draw("done");
poly.fill("#00B39F");
showCytoArray();
}
-
if (e.ctrlKey && e.key.toLowerCase() === "z") {
const points = getPlottedPoints(poly);
if (!points) return;
@@ -155,20 +147,17 @@ const ShapeBuilder = () => {
setError("Canvas reference not found");
return;
}
-
try {
- const draw = SVG()
+ const drawInstance = SVG()
.addTo(boardRef.current)
.size("100%", "100%")
.polygon()
.draw()
.attr({ stroke: "#00B39F", "stroke-width": 1, fill: "none" });
-
- draw.draw("param", "snapToGrid", 16);
- draw.on("drawstart", attachKeyListeners);
- draw.on("drawdone", detachKeyListeners);
-
- polyRef.current = draw;
+ drawInstance.draw("param", "snapToGrid", 16);
+ drawInstance.on("drawstart", attachKeyListeners);
+ drawInstance.on("drawdone", detachKeyListeners);
+ polyRef.current = drawInstance;
setError(null);
} catch (err) {
setError(`Failed to initialize drawing: ${err.message}`);
@@ -177,23 +166,23 @@ const ShapeBuilder = () => {
const clearShape = () => {
const poly = polyRef.current;
- if (!poly) return;
-
- poly.draw("cancel");
- poly.remove();
- detachKeyListeners();
- polyRef.current = null;
- basePointsRef.current = null;
+ if (poly) {
+ poly.draw("cancel");
+ poly.remove();
+ detachKeyListeners();
+ polyRef.current = null;
+ basePointsRef.current = null;
+ }
setResult("");
setScale(1);
setCurrentPreset(1);
+ setModalOpen(false);
initializeDrawing();
};
const closeShape = () => {
const poly = polyRef.current;
if (!poly) return;
-
poly.draw("done");
poly.fill("#00B39F");
const points = getPlottedPoints(poly);
@@ -217,6 +206,16 @@ const ShapeBuilder = () => {
return (
+ {/* ── Coordinates Modal ─────────────────────────────────────────────── */}
+ {modalOpen && result && (
+ setModalOpen(false)}
+ onClear={clearShape}
+ />
+ )}
+
{
+ {/* Re-open modal button — only shown when coordinates exist */}
+ {result && (
+
+ )}
@@ -261,9 +270,7 @@ const ShapeBuilder = () => {
aria-label="Scale preset"
sx={{
color: "#fff",
- "& .MuiSelect-icon": {
- color: "#fff"
- }
+ "& .MuiSelect-icon": { color: "#fff" }
}}
>
{SCALE_PRESETS.map((preset) => (
@@ -295,12 +302,13 @@ const ShapeBuilder = () => {
+ {/* ── Fallback output box (kept for accessibility / non-JS contexts) ── */}
Polygon Coordinates (SVG format):
-
+
{result.trim() && (
{
-
+
From 7f0fda45d9e7805dcab204e4a29ff39292887d36 Mon Sep 17 00:00:00 2001
From: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
Date: Sat, 11 Apr 2026 19:17:41 +0530
Subject: [PATCH 2/5] fix(ux): make 'View Coordinates' button visible in dark
mode
The 'View Coordinates' button text was invisible in dark mode because
MUI's outlined variant inherits text color from the background theme.
Fixed by explicitly setting:
- color: "#00B39F" (Meshery brand color)
- borderColor: "#00B39F"
- Hover state with subtle background tint
Button now clearly visible in both light and dark themes.
Signed-off-by: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
---
site/src/components/ShapeBuilder/index.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/site/src/components/ShapeBuilder/index.js b/site/src/components/ShapeBuilder/index.js
index 0f7b455..a2eb3e7 100644
--- a/site/src/components/ShapeBuilder/index.js
+++ b/site/src/components/ShapeBuilder/index.js
@@ -255,6 +255,14 @@ const ShapeBuilder = ({ theme }) => {
variant="outlined"
onClick={() => setModalOpen(true)}
title="View polygon coordinates"
+ sx={{
+ color: "#00B39F",
+ borderColor: "#00B39F",
+ "&:hover": {
+ borderColor: "#00B39F",
+ backgroundColor: "rgba(0, 179, 159, 0.08)",
+ },
+ }}
>
View Coordinates
From 30e66cad4539d60e02f545bc845da786070266d5 Mon Sep 17 00:00:00 2001
From: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
Date: Sun, 12 Apr 2026 18:21:08 +0530
Subject: [PATCH 3/5] fix(shape-builder): copy button not visible with long
coordinates
Fixes #89
Repositioned CopyButton from right: -25px (outside textarea) to
right: 8px (inside textarea) with z-index: 1. Added padding-right
to textarea so coordinate text does not overlap the button.
The button now includes a visible background and border to maintain
consistency and visibility within the textarea bounds.
Signed-off-by: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
---
.../ShapeBuilder/shapeBuilder.styles.js | 21 +++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/site/src/components/ShapeBuilder/shapeBuilder.styles.js b/site/src/components/ShapeBuilder/shapeBuilder.styles.js
index aa53f48..064518a 100644
--- a/site/src/components/ShapeBuilder/shapeBuilder.styles.js
+++ b/site/src/components/ShapeBuilder/shapeBuilder.styles.js
@@ -103,7 +103,7 @@ export const OutputBox = styled.div`
textarea {
width: 100%;
height: 80px;
- padding: 1rem;
+ padding: 1rem 2.5rem 1rem 1rem;
border: 1px solid ${({ theme }) => theme.border || "#24292E"};
border-radius: 0.5rem;
background-color: ${({ theme }) => theme.body || "#181B1F"};
@@ -111,6 +111,7 @@ export const OutputBox = styled.div`
resize: none;
font-family: monospace;
font-size: 0.95rem;
+ box-sizing: border-box;
}
.error {
@@ -121,16 +122,24 @@ export const OutputBox = styled.div`
export const CopyButton = styled.button`
position: absolute;
- top: 0;
- right: -25px;
- background: none;
- border: none;
+ top: 8px;
+ right: 8px;
+ background: ${({ theme }) => theme.body || "#181B1F"};
+ border: 1px solid ${({ theme }) => theme.border || "#24292E"};
+ border-radius: 4px;
cursor: pointer;
- padding: 4px;
+ padding: 4px 6px;
display: flex;
align-items: center;
font-size: 12px;
color: ${({ theme }) => theme.text};
+ opacity: 0.85;
+ transition: opacity 0.15s;
+ z-index: 1;
+
+ &:hover {
+ opacity: 1;
+ }
svg {
color: ${({ theme }) => theme.text};
From 5071b5906bab511193fcad32625c2abdb08ac888 Mon Sep 17 00:00:00 2001
From: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
Date: Mon, 13 Apr 2026 22:54:42 +0530
Subject: [PATCH 4/5] Delete
site/src/components/ShapeBuilder/CoordinatesModal.js
Signed-off-by: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
---
.../ShapeBuilder/CoordinatesModal.js | 238 ------------------
1 file changed, 238 deletions(-)
delete mode 100644 site/src/components/ShapeBuilder/CoordinatesModal.js
diff --git a/site/src/components/ShapeBuilder/CoordinatesModal.js b/site/src/components/ShapeBuilder/CoordinatesModal.js
deleted file mode 100644
index 33aa620..0000000
--- a/site/src/components/ShapeBuilder/CoordinatesModal.js
+++ /dev/null
@@ -1,238 +0,0 @@
-import React, { useState } from "react";
-import styled from "styled-components";
-import { CopyIcon } from "@sistent/sistent";
-
-// ─── Overlay ────────────────────────────────────────────────────────────────
-const Overlay = styled.div`
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.65);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1300;
- /* subtle entrance */
- animation: fadeIn 0.18s ease;
-
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
-`;
-
-// ─── Modal Card ──────────────────────────────────────────────────────────────
-const ModalCard = styled.div`
- background: ${({ theme }) => theme.body || "#1e2227"};
- border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
- border-radius: 12px;
- padding: 2rem;
- width: min(560px, 92vw);
- box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5);
- animation: slideUp 0.2s ease;
- position: relative;
-
- @keyframes slideUp {
- from { transform: translateY(24px); opacity: 0; }
- to { transform: translateY(0); opacity: 1; }
- }
-`;
-
-// ─── Header row ─────────────────────────────────────────────────────────────
-const Header = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 0.25rem;
-`;
-
-const Title = styled.h2`
- font-size: 1.15rem;
- font-weight: 600;
- color: ${({ theme }) => theme.text || "#ffffff"};
- margin: 0;
-`;
-
-const CloseButton = styled.button`
- background: none;
- border: none;
- cursor: pointer;
- color: ${({ theme }) => theme.text || "#ffffff"};
- font-size: 1.4rem;
- line-height: 1;
- padding: 2px 6px;
- border-radius: 4px;
- opacity: 0.7;
- transition: opacity 0.15s;
-
- &:hover { opacity: 1; }
-`;
-
-// ─── Sub-label ───────────────────────────────────────────────────────────────
-const SubLabel = styled.p`
- font-size: 0.8rem;
- color: ${({ theme }) => theme.textMuted || "#8b949e"};
- margin: 0 0 1rem 0;
-`;
-
-// ─── Code block ──────────────────────────────────────────────────────────────
-const CodeBlock = styled.div`
- position: relative;
- background: ${({ theme }) => theme.codeBackground || "#0d1117"};
- border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
- border-radius: 8px;
- padding: 1rem 3rem 1rem 1rem;
- margin-bottom: 1.25rem;
-`;
-
-const CoordText = styled.pre`
- color: #00b39f;
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
- font-size: 0.85rem;
- line-height: 1.6;
- margin: 0;
- white-space: pre-wrap;
- word-break: break-all;
-`;
-
-const InlineCopyButton = styled.button`
- position: absolute;
- top: 10px;
- right: 10px;
- background: none;
- border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
- border-radius: 6px;
- cursor: pointer;
- padding: 5px 8px;
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 0.72rem;
- color: ${({ theme }) => theme.text || "#ffffff"};
- opacity: 0.75;
- transition: opacity 0.15s, background 0.15s;
-
- &:hover {
- opacity: 1;
- background: ${({ theme }) => theme.border || "#2d3139"};
- }
-
- svg {
- width: 14px;
- height: 14px;
- fill: currentColor;
- }
-`;
-
-// ─── Action row ──────────────────────────────────────────────────────────────
-const ActionRow = styled.div`
- display: flex;
- gap: 0.75rem;
- justify-content: flex-end;
- flex-wrap: wrap;
-`;
-
-const ActionButton = styled.button`
- padding: 0.55rem 1.3rem;
- border-radius: 8px;
- font-size: 0.9rem;
- font-weight: 500;
- cursor: pointer;
- transition: background 0.15s, opacity 0.15s;
-
- &.primary {
- background: #00b39f;
- color: #fff;
- border: none;
- &:hover { background: #009684; }
- }
-
- &.secondary {
- background: transparent;
- color: ${({ theme }) => theme.text || "#ffffff"};
- border: 1px solid ${({ theme }) => theme.border || "#2d3139"};
- &:hover { opacity: 0.75; }
- }
-`;
-
-// ─── Component ───────────────────────────────────────────────────────────────
-const CoordinatesModal = ({ coordinates, onClose, onClear, theme }) => {
- const [copied, setCopied] = useState(false);
-
- const handleCopy = async () => {
- try {
- await navigator.clipboard.writeText(coordinates);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- } catch (err) {
- console.error("Failed to copy:", err);
- }
- };
-
- // Close on overlay click (not on card click)
- const handleOverlayClick = (e) => {
- if (e.target === e.currentTarget) onClose();
- };
-
- return (
-
-
- {/* ── Header ── */}
-
-
- Polygon Coordinates
-
-
- ×
-
-
-
-
- SVG-format values normalized to the [−1, 1] range. Copy and paste
- into your Meshery Component's
- polygon shape field.
-
-
- {/* ── Code block ── */}
-
- {coordinates}
-
- {copied ? (
- "✓ Copied"
- ) : (
- <>
-
- Copy
- >
- )}
-
-
-
- {/* ── Actions ── */}
-
-
- Clear & Start Over
-
-
- Done
-
-
-
-
- );
-};
-
-export default CoordinatesModal;
From aa09aeace97327995ccc2743fe8a478635704180 Mon Sep 17 00:00:00 2001
From: MANISH KUMAR <146671113+manishpatel00@users.noreply.github.com>
Date: Sun, 19 Apr 2026 01:57:57 +0530
Subject: [PATCH 5/5] refactor(shape-builder): enhance copy button styling and
accessibility
- Increase z-index from 1 to 10 for better layer management
- Remove opacity-based visibility in favor of hover color change
- Add hover state with primary color background
- Add focus-visible state for keyboard accessibility
- Increase min-width/min-height to 32px for click target size
- Add white-space: nowrap to prevent text wrapping
- Improve SVG sizing with explicit width/height
- Use currentColor for better color inheritance
- Add disabled state handling
- Improve transitions for smoother interaction
---
.../ShapeBuilder/shapeBuilder.styles.js | 35 ++++++++++++++-----
1 file changed, 26 insertions(+), 9 deletions(-)
diff --git a/site/src/components/ShapeBuilder/shapeBuilder.styles.js b/site/src/components/ShapeBuilder/shapeBuilder.styles.js
index 064518a..c2e91b6 100644
--- a/site/src/components/ShapeBuilder/shapeBuilder.styles.js
+++ b/site/src/components/ShapeBuilder/shapeBuilder.styles.js
@@ -128,22 +128,39 @@ export const CopyButton = styled.button`
border: 1px solid ${({ theme }) => theme.border || "#24292E"};
border-radius: 4px;
cursor: pointer;
- padding: 4px 6px;
+ padding: 6px 8px;
display: flex;
align-items: center;
+ justify-content: center;
font-size: 12px;
- color: ${({ theme }) => theme.text};
- opacity: 0.85;
- transition: opacity 0.15s;
- z-index: 1;
+ color: ${({ theme }) => theme.text || "#fff"};
+ transition: all 0.2s ease-in-out;
+ z-index: 10;
+ min-width: 32px;
+ min-height: 32px;
+ white-space: nowrap;
+
+ &:hover:not(:disabled) {
+ background-color: ${({ theme }) => theme.primary || "#00B39F"};
+ border-color: ${({ theme }) => theme.primary || "#00B39F"};
+ color: #fff;
+ }
+
+ &:focus-visible {
+ outline: 2px solid ${({ theme }) => theme.primary || "#00B39F"};
+ outline-offset: 2px;
+ }
- &:hover {
- opacity: 1;
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
}
svg {
- color: ${({ theme }) => theme.text};
- fill: ${({ theme }) => theme.text};
+ color: inherit;
+ fill: currentColor;
+ width: 18px;
+ height: 18px;
}
`;