Skip to content

Commit 5952347

Browse files
committed
feat: optimize UI/UX and perf
1 parent 4d30127 commit 5952347

14 files changed

Lines changed: 85 additions & 28 deletions

.changeset/old-bottles-sin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"rozenite-growthbook-plugin": minor
3+
---
4+
5+
Optimize UI/UX and performance

packages/plugin/src/ui/components/attribute-inputs/enum-input.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from 'react'
12
import CreatableSelect from 'react-select/creatable'
23

34
import type { SelectOption } from './select-styles'
@@ -10,13 +11,15 @@ interface EnumInputProps {
1011
}
1112

1213
export const EnumInput = ({ onChange, options, value }: EnumInputProps) => {
13-
const selectOptions: SelectOption[] = options.map((o) => ({ label: o, value: o }))
14+
const selectOptions = useMemo<SelectOption[]>(() => options.map((o) => ({ label: o, value: o })), [options])
15+
16+
const selectValue = useMemo(() => ({ label: value, value }), [value])
1417

1518
return (
1619
<CreatableSelect<SelectOption>
1720
styles={selectStyles}
1821
options={selectOptions}
19-
value={{ label: value, value }}
22+
value={selectValue}
2023
onChange={(opt) => {
2124
onChange(opt ? opt.value : '')
2225
}}

packages/plugin/src/ui/components/attribute-inputs/number-array-input.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from 'react'
12
import CreatableSelect from 'react-select/creatable'
23

34
import type { SelectOption } from './select-styles'
@@ -9,7 +10,7 @@ interface NumberArrayInputProps {
910
}
1011

1112
export const NumberArrayInput = ({ onChange, value }: NumberArrayInputProps) => {
12-
const selectValue = value.map((v) => ({ label: v.toString(), value: v.toString() }))
13+
const selectValue = useMemo(() => value.map((v) => ({ label: v.toString(), value: v.toString() })), [value])
1314

1415
return (
1516
<CreatableSelect<SelectOption, true>

packages/plugin/src/ui/components/attribute-inputs/string-array-input.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { useMemo } from 'react'
12
import CreatableSelect from 'react-select/creatable'
23

34
import { selectStyles } from './select-styles'
45
import type { SelectOption } from './select-styles'
56

67
export const StringArrayInput = ({ onChange, value }: { onChange: (value: string[]) => void; value: string[] }) => {
7-
const selectValue = value.map((v) => ({ label: v, value: v }))
8+
const selectValue = useMemo(() => value.map((v) => ({ label: v, value: v })), [value])
89

910
return (
1011
<CreatableSelect<SelectOption, true>

packages/plugin/src/ui/components/attributes-tab.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useQueryErrorResetBoundary } from '@tanstack/react-query'
22
import { type } from 'arktype'
3-
import { Suspense, useRef, useState } from 'react'
3+
import { Suspense, useMemo, useRef, useState } from 'react'
44
import type { FallbackProps } from 'react-error-boundary'
55
import { ErrorBoundary } from 'react-error-boundary'
66

@@ -136,12 +136,20 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => {
136136

137137
const { archetypes, attributeSchema } = useData()
138138

139-
const schemaByProperty = new Map(attributeSchema.map((attr) => [attr.property, attr]))
139+
const schemaByProperty = useMemo(
140+
() => new Map(attributeSchema.map((attr) => [attr.property, attr])),
141+
[attributeSchema]
142+
)
143+
144+
const overriddenKeys = useMemo(() => {
145+
const original = originalAttributesRef.current
140146

141-
const isOverridden = (key: string) =>
142-
JSON.stringify(attributes[key]) !== JSON.stringify(originalAttributesRef.current[key])
147+
return new Set(
148+
Object.keys(attributes).filter((key) => JSON.stringify(attributes[key]) !== JSON.stringify(original[key]))
149+
)
150+
}, [attributes])
143151

144-
const hasOverrides = Object.keys(attributes).some(isOverridden)
152+
const hasOverrides = overriddenKeys.size > 0
145153

146154
const handleFieldChange = (key: string, value: unknown) => {
147155
onSave({ ...attributes, [key]: value })
@@ -322,9 +330,11 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => {
322330
spellCheck={false}
323331
/>
324332

325-
{jsonError !== null && (
326-
<div className="mt-1 rounded bg-red-500/10 px-3 py-2 text-xs text-red-400">{jsonError}</div>
327-
)}
333+
<div aria-live="polite" aria-atomic="true" className="mt-1 min-h-0">
334+
{jsonError !== null && (
335+
<div className="rounded bg-panel-error-bg px-3 py-2 text-xs text-panel-error">{jsonError}</div>
336+
)}
337+
</div>
328338

329339
<div className="mt-2 flex justify-end gap-2">
330340
<button
@@ -365,11 +375,11 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => {
365375
</div>
366376

367377
<div className="w-5 shrink-0">
368-
{isOverridden(key) && (
378+
{overriddenKeys.has(key) && (
369379
<button
370380
type="button"
371381
className="text-override hover:text-override/80"
372-
title="Reset to original"
382+
aria-label="Reset to original"
373383
onClick={() => {
374384
handleReset(key)
375385
}}>
@@ -404,8 +414,8 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => {
404414
<div className="w-5 shrink-0">
405415
<button
406416
type="button"
407-
className="text-red-400 hover:text-red-300"
408-
title="Remove attribute"
417+
className="text-panel-error hover:text-panel-error/80"
418+
aria-label="Remove attribute"
409419
onClick={() => {
410420
handleDeleteCustom(key)
411421
}}>
@@ -471,6 +481,7 @@ const AttributesTabInner = ({ attributes, onSave }: AttributesTabProps) => {
471481

472482
<button
473483
type="button"
484+
aria-label="Cancel"
474485
className="text-sm text-panel-text-secondary hover:text-panel-text"
475486
onClick={() => {
476487
setShowAddForm(false)

packages/plugin/src/ui/components/error.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const ErrorFallback = ({ error, onReset }: ErrorFallbackProps) => {
4949
const technicalError = getTechnicalErrorDetails(error)
5050

5151
return (
52-
<div className="flex h-screen flex-col items-center justify-center gap-4 px-4 text-center text-red-400">
52+
<div className="flex h-screen flex-col items-center justify-center gap-4 px-4 text-center text-panel-error">
5353
<h1 className="text-2xl font-bold">An error occurred while receiving data from GrowthBook</h1>
5454

5555
<p className="max-w-2xl text-sm text-panel-text-secondary">{technicalError.message}</p>
@@ -65,7 +65,7 @@ export const ErrorFallback = ({ error, onReset }: ErrorFallbackProps) => {
6565
</details>
6666

6767
<button
68-
className="rounded bg-red-500 px-4 py-2 text-white"
68+
className="rounded bg-panel-error px-4 py-2 text-panel-bg"
6969
onClick={() => {
7070
onReset?.()
7171
resetBoundary()

packages/plugin/src/ui/components/experiment-row.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const ExperimentRow = ({
2727
</td>
2828
<td className="px-3 py-2">
2929
<select
30+
aria-label={`Variation for ${experiment.key}`}
3031
className="rounded border border-panel-border bg-panel-surface px-2 py-1 text-sm text-panel-text"
3132
value={activeVariation}
3233
onChange={(e) => {
@@ -50,7 +51,9 @@ export const ExperimentRow = ({
5051
<td className="px-3 py-2 text-center">
5152
<span
5253
className={`inline-block rounded px-2 py-0.5 text-xs ${
53-
experiment.inExperiment ? 'bg-green-500/10 text-green-400' : 'bg-panel-surface text-panel-text-secondary'
54+
experiment.inExperiment
55+
? 'bg-panel-success-bg text-panel-success'
56+
: 'bg-panel-surface text-panel-text-secondary'
5457
}`}>
5558
{experiment.inExperiment ? 'In experiment' : 'Not in experiment'}
5659
</span>

packages/plugin/src/ui/components/experiments-tab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const ExperimentsTab = ({
4040
<div className="mb-3 flex items-center gap-3">
4141
<input
4242
type="text"
43+
aria-label="Filter experiments"
4344
placeholder="Filter experiments..."
4445
className="flex-1 rounded border border-panel-border bg-panel-surface px-3 py-1.5 text-sm text-panel-text placeholder:text-panel-text-secondary"
4546
value={search}

packages/plugin/src/ui/components/feature-row.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const FeatureRow = ({
6060
<div className="flex items-center gap-2">
6161
<input
6262
type="text"
63+
aria-label={`Edit value for ${feature.key}`}
6364
className="flex-1 rounded border border-panel-border bg-panel-bg px-2 py-1 text-sm text-panel-text"
6465
value={editValue}
6566
onChange={(e) => {
@@ -90,22 +91,28 @@ export const FeatureRow = ({
9091
</button>
9192
</div>
9293
) : (
93-
<span className="cursor-pointer" onClick={isBoolean ? handleToggle : handleEditStart}>
94+
<button
95+
type="button"
96+
className="inline-flex cursor-pointer items-center leading-none"
97+
role={isBoolean ? 'switch' : undefined}
98+
aria-checked={isBoolean ? displayValue : undefined}
99+
aria-label={isBoolean ? `Toggle ${feature.key}` : `Edit value for ${feature.key}`}
100+
onClick={isBoolean ? handleToggle : handleEditStart}>
94101
{isBoolean ? (
95102
<span
96-
className={`relative inline-block h-4 w-8 rounded-full transition-colors ${
97-
displayValue ? 'bg-green-500' : 'bg-panel-border'
103+
className={`relative inline-block h-4 w-8 overflow-hidden rounded-full transition-colors ${
104+
displayValue ? 'bg-panel-success' : 'bg-panel-border'
98105
}`}>
99106
<span
100-
className={`absolute top-0.5 h-3 w-3 rounded-full bg-white transition-transform ${
107+
className={`absolute left-0 top-0.5 h-3 w-3 rounded-full bg-white transition-transform ${
101108
displayValue ? 'translate-x-4' : 'translate-x-0.5'
102109
}`}
103110
/>
104111
</span>
105112
) : (
106113
<InlineValue value={displayValue} />
107114
)}
108-
</span>
115+
</button>
109116
)}
110117
</td>
111118
<td className="px-3 py-2">

packages/plugin/src/ui/components/features-tab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const FeaturesTab = ({
3838
<div className="mb-3 flex items-center gap-3">
3939
<input
4040
type="text"
41+
aria-label="Filter features"
4142
placeholder="Filter features..."
4243
className="flex-1 rounded border border-panel-border bg-panel-surface px-3 py-1.5 text-sm text-panel-text placeholder:text-panel-text-secondary"
4344
value={search}

0 commit comments

Comments
 (0)