Skip to content

Commit 89673f5

Browse files
committed
Significantly improved editing experience; Updated form to change variable value on field change
1 parent 9ed0600 commit 89673f5

File tree

5 files changed

+227
-54
lines changed

5 files changed

+227
-54
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Col, Form, InputNumber, Row, Slider } from "antd";
2+
3+
export const IntegerStep = ({
4+
name,
5+
defaultValue,
6+
onChange,
7+
}: {
8+
name: string;
9+
defaultValue: string;
10+
onChange: (name: string, value: string) => void;
11+
}) => {
12+
const form = Form.useFormInstance();
13+
const inputValue = Form.useWatch(name, { form, preserve: true });
14+
15+
const parseStringValue = (stringValue: string) => {
16+
const valueAsInteger = parseFloat(stringValue.replace(/^[^\d.-]+/, "")); // Replace all leading non-digits (except decimal points and dashes) with nothing
17+
const valueSuffix = stringValue.replace(/^[\d.-]+/, "").trim(); // Replace all leading digits (and decimal points and dashes) with nothing
18+
19+
return { value: valueAsInteger, suffix: valueSuffix };
20+
};
21+
22+
const { value: numberCurrentValue, suffix } = parseStringValue(
23+
inputValue || defaultValue
24+
);
25+
26+
const { value: numberDefaultValue } = parseStringValue(defaultValue);
27+
28+
const onStepChange = (value: number | null) => {
29+
if (value === null) return; // Handle null case for InputNumber
30+
31+
const valueAsString = `${value}${suffix || ""}`;
32+
if (inputValue !== valueAsString) {
33+
form.setFieldValue(name, valueAsString);
34+
}
35+
36+
if (onChange) {
37+
onChange(name, valueAsString);
38+
}
39+
};
40+
41+
return (
42+
<Row>
43+
<Col span={14}>
44+
<Slider
45+
min={numberDefaultValue / 2}
46+
max={numberDefaultValue * 2}
47+
step={(numberDefaultValue * 2 - numberDefaultValue / 2) / 100}
48+
value={numberCurrentValue}
49+
onChange={onStepChange}
50+
/>
51+
</Col>
52+
<Col span={10}>
53+
<InputNumber
54+
style={{ margin: "0 16px" }}
55+
addonAfter={suffix}
56+
value={numberCurrentValue}
57+
onChange={onStepChange}
58+
/>
59+
</Col>
60+
</Row>
61+
);
62+
};
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
.token-details {
22
&__selected-target {
3-
border: 2px solid red !important;
3+
border: 3px solid red;
44
cursor: pointer;
5+
6+
&--frozen {
7+
border-color: green;
8+
}
59
}
610
}
11+
12+
.ant-color-picker {
13+
z-index: 100000;
14+
}

src/components/TokenDetails/TokenDetails.tsx

Lines changed: 107 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,146 @@
11
import _ from "lodash";
22
import { type ReactNode, useEffect, useState } from "react";
3-
import { Button, Typography } from "antd";
3+
import { Button, Flex, Form, Typography } from "antd";
44
import { variablesUsedByElement } from "../../utils/variable-parsing/findVariablesUsedByElement";
55
import { transformVariableToReact } from "../../utils/variable-parsing/transformVariableToReact";
66
import "./TokenDetails.scss";
7+
import { changeVariableValue } from "../../utils/variable-updates/changeVariableValue";
8+
import type { Color } from "antd/es/color-picker";
79

810
const { Text } = Typography;
911

1012
export const TokenDetails = () => {
1113
const [target, setTarget] = useState<HTMLElement | null>(null);
1214
const [frozen, setFrozen] = useState(false); // State to manage if the target is frozen
1315
const [editable, setEditable] = useState(false); // State to manage if the variable values should be editable
16+
const [tokenEditingForm] = Form.useForm();
1417

15-
const saveVariableValues = () => {
18+
const onValuesChange = (values: Record<string, string | Color>) => {
19+
for (const [key, value] of Object.entries(values)) {
20+
if (typeof value === "object") {
21+
if ("toCssString" in value) {
22+
// If the value is a ColorPicker, convert it to a CSS string
23+
changeVariableValue(key, value.toCssString());
24+
continue;
25+
}
26+
}
27+
changeVariableValue(key, value);
28+
}
29+
};
30+
31+
const onFinish = () => {
1632
setEditable(false);
33+
setFrozen(false);
1734
};
1835

1936
const targetTokenDetails = (target: HTMLElement | null): ReactNode => {
20-
if (target) {
21-
try {
22-
const elementName = target.tagName;
23-
const elementVariables = variablesUsedByElement(target);
24-
return (
25-
<>
26-
<Text strong>{_.lowerCase(elementName)}</Text>
27-
<br />
28-
<br />
37+
if (!target) {
38+
return <h4>No target selected </h4>;
39+
}
40+
41+
try {
42+
const elementName = target.tagName;
43+
const elementVariables = variablesUsedByElement(target);
44+
return (
45+
<>
46+
<Text strong>{_.lowerCase(elementName)}</Text>
47+
<br />
48+
<br />
49+
<Form
50+
form={tokenEditingForm}
51+
name="variableValues"
52+
onValuesChange={onValuesChange}
53+
onFinish={onFinish}
54+
>
2955
{elementVariables.map((variable) =>
30-
transformVariableToReact(variable, editable)
56+
transformVariableToReact(
57+
variable,
58+
editable,
59+
(name: string, value: string) => {
60+
changeVariableValue(name, value);
61+
}
62+
)
3163
)}
3264
<br />
33-
<Button
34-
onClick={() =>
35-
editable ? saveVariableValues() : setEditable(true)
36-
}
37-
type="primary"
38-
size="small"
39-
>
40-
{editable ? "Save" : "Edit"}
41-
</Button>
42-
<br />
43-
<br />
44-
<Button
45-
onClick={() => setFrozen(false)}
46-
color="cyan"
47-
variant="solid"
48-
size="small"
49-
>
50-
Unfreeze
51-
</Button>
52-
</>
53-
);
54-
} catch (e) {
55-
console.log(e);
56-
}
57-
} else {
58-
return <h4>No target selected </h4>;
65+
<Flex gap={8}>
66+
{editable ? (
67+
<Form.Item noStyle>
68+
<Button type="primary" size="small" htmlType="submit">
69+
Save
70+
</Button>
71+
</Form.Item>
72+
) : null}
73+
{editable ? null : (
74+
<Button
75+
onClick={() => setEditable(true)}
76+
type="primary"
77+
size="small"
78+
>
79+
Edit
80+
</Button>
81+
)}
82+
{frozen ? (
83+
<>
84+
<br />
85+
<Button
86+
id="unfreeze-button"
87+
onClick={() => {
88+
setFrozen(false);
89+
}}
90+
color="cyan"
91+
variant="solid"
92+
size="small"
93+
>
94+
Unfreeze
95+
</Button>
96+
</>
97+
) : null}
98+
</Flex>
99+
</Form>
100+
</>
101+
);
102+
} catch (e) {
103+
console.log(e);
59104
}
60105
};
61106

107+
useEffect(() => {
108+
if (target) {
109+
if (frozen) {
110+
target.classList.add("token-details__selected-target--frozen");
111+
} else {
112+
target.classList.remove("token-details__selected-target--frozen");
113+
}
114+
}
115+
}, [target, frozen]);
116+
62117
useEffect(() => {
63118
const interceptClick = (event: MouseEvent) => {
64119
if (!(event.target instanceof HTMLElement)) {
65120
return;
66121
}
67122

68-
if (event.target.matches(".tokenInspector *")) {
69-
console.log("Click on token inspector, NOT freezing target");
123+
if (
124+
event.target.matches(".tokenInspector *") ||
125+
event.target.matches("#unfreeze-button *")
126+
) {
70127
// If the click is not on the token inspector, do not freeze
71128
return;
72129
}
73130

74131
event.preventDefault();
75132
event.stopPropagation();
76133

134+
if (frozen) {
135+
if (event.target.matches(".token-details__selected-target")) {
136+
// If the click is on the selected target, unfreeze it
137+
setFrozen(false);
138+
return;
139+
}
140+
// If the target is currently frozen, do not change anything
141+
return;
142+
}
143+
77144
setFrozen(true);
78145
};
79146

@@ -99,7 +166,6 @@ export const TokenDetails = () => {
99166
el.classList.remove("token-details__selected-target");
100167
}
101168
});
102-
console.log("Changing target!");
103169
setTarget(newTarget);
104170
}
105171
};

src/utils/variable-parsing/transformVariableToReact.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,27 @@ const { Text } = Typography;
77

88
export const transformVariableToReact = (
99
variable: Variable,
10-
editable: boolean = false
10+
editable: boolean = false,
11+
onChange?: (name: string, value: string) => void
1112
): ReactNode => {
13+
const usedIn = (
14+
<>
15+
(used in <Text strong>{variable.property}</Text>)
16+
</>
17+
);
1218
return (
1319
<div>
14-
{variable.formattedName ?? variable.name}: <code>{variable.value}</code>{" "}
15-
(used in{" "}
16-
<Text strong>
17-
{editable ? variableToFormInput(variable) : variable.property}
18-
</Text>
19-
)
20+
{variable.formattedName ?? variable.name}:{" "}
21+
{editable && onChange ? (
22+
<>
23+
{usedIn} <br />
24+
{variableToFormInput(variable, onChange)}
25+
</>
26+
) : (
27+
<>
28+
<code>{variable.value}</code> {usedIn}
29+
</>
30+
)}
2031
<br />
2132
</div>
2233
);
Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,42 @@
11
import type { ReactNode } from "react";
2-
import { ColorPicker, Input, InputNumber } from "antd";
2+
import { ColorPicker, Form, InputNumber } from "antd";
33
import type { Variable } from "../../types/variables";
4+
import { IntegerStep } from "../../components/TokenDetails/IntegerStep";
45

5-
export const variableToFormInput = (variable: Variable): ReactNode => {
6+
const formItemWrapper = (
7+
formInput: ReactNode,
8+
variableName: string,
9+
initialValue: string | number
10+
) => (
11+
<Form.Item name={variableName} initialValue={initialValue} noStyle>
12+
{formInput}
13+
</Form.Item>
14+
);
15+
16+
export const variableToFormInput = (
17+
variable: Variable,
18+
onChange: (name: string, value: string) => void
19+
): ReactNode => {
620
const { value } = variable;
21+
22+
let formInput: ReactNode | null = null;
23+
24+
// Determine the type of input based on the value
725
if (typeof value === "string") {
826
if (value.startsWith("#") || value.startsWith("rgb")) {
9-
return <ColorPicker defaultValue={value} showText size="small" />;
27+
formInput = <ColorPicker showText size="small" />;
1028
} else {
11-
return <Input defaultValue={value} />;
29+
return (
30+
<IntegerStep
31+
defaultValue={value}
32+
name={variable.name}
33+
onChange={onChange}
34+
/>
35+
);
1236
}
1337
} else if (typeof value === "number") {
14-
return <InputNumber defaultValue={value} />;
38+
formInput = <InputNumber />;
1539
}
40+
41+
return formItemWrapper(formInput, variable.name, value);
1642
};

0 commit comments

Comments
 (0)