ReviewOS

also looking at this

stacks/stx

fix(components): Progress max/indeterminate and Tooltip disabled are live props

#2008
Merged glennmichael123 wants to merge fix/progress-tooltip-reactive-props into main
5 files +242 -17
packages/components/src/ui/progress/Progress.stxmodified+35-6
Changes to packages/components/src/ui/progress/Progress.stx
@@ -42,16 +42,42 @@ export const circularBarColor = color === 'primary' ? 'stroke-info'
4242</script>
4343
4444<script client>
45const max = {{ max }}
46const indeterminate = {{ indeterminate }}
4745const circumference = {{ circumference }}
48// Reactive prop binding — parent can drive `value` via signals. See #1704.
46const circularBarColor = {{ circularBarColor }}
47
48/*
49 * `value` has been a live prop since #1704. `max` and `indeterminate` were not,
50 * and they are bound for the same reasons:
51 *
52 * :max="total()" the denominator is often unknown until the
53 * response that supplies it arrives
54 * :indeterminate="!total()" and the bar starts indeterminate precisely
55 * BECAUSE the total is not known yet
56 *
57 * Frozen, those two could only ever describe the state the page was built in,
58 * so a bar could never stop spinning once the total showed up. See #1997 for
59 * the same freeze on Button.
60 */
61/*
62 * The locals are deliberately NOT named `max` / `indeterminate`.
63 *
64 * A client-script name that matches a server-script export stops the server
65 * interpolating that name in the template: the `animate-spin` ternary below
66 * rendered as literal text rather than being evaluated. The attribute
67 * useReactiveProp watches is its first argument, so the local is free to be
68 * called something else — and has to be.
69 */
4970const value = useReactiveProp('value', {{ value }})
71const maxValue = useReactiveProp('max', {{ max }})
72const isIndeterminate = useReactiveProp('indeterminate', {{ indeterminate }})
5073
51const percentage = derived(() => Math.min(Math.max((value() / max) * 100, 0), 100))
74const percentage = derived(() => Math.min(Math.max((value() / maxValue()) * 100, 0), 100))
5275const percentLabel = derived(() => `${Math.round(percentage())}%`)
53const linearWidth = derived(() => indeterminate ? '30%' : `${percentage()}%`)
54const dashOffset = derived(() => indeterminate ? 0 : circumference - (percentage() / 100) * circumference)
76const linearWidth = derived(() => isIndeterminate() ? '30%' : `${percentage()}%`)
77const dashOffset = derived(() => isIndeterminate() ? 0 : circumference - (percentage() / 100) * circumference)
78// The spin is part of what `indeterminate` MEANS, so it follows the signal
79// rather than the server's snapshot of it.
80const circularBarClass = derived(() => `${circularBarColor} transition-all duration-300 ease-in-out ${isIndeterminate() ? 'animate-spin' : ''}`.trim())
5581
5682defineExpose({ value, setValue: (v) => value.set(v) })
5783</script>
@@ -70,6 +96,7 @@ defineExpose({ value, setValue: (v) => value.set(v) })
7096 :aria-valuenow="value()"
7197 aria-valuemin="0"
7298 aria-valuemax="function max(array) { if (array.length === 0) throw Error("Cannot compute max of an empty array"); return Math.max(...array); }"
99 :aria-valuemax="maxValue()"
73100 >
74101 <div
75102 class=""
@@ -88,6 +115,7 @@ defineExpose({ value, setValue: (v) => value.set(v) })
88115 :aria-valuenow="value()"
89116 aria-valuemin="0"
90117 aria-valuemax="function max(array) { if (array.length === 0) throw Error("Cannot compute max of an empty array"); return Math.max(...array); }"
118 :aria-valuemax="maxValue()"
91119 >
92120 <circle
93121 class=""
@@ -99,6 +127,7 @@ defineExpose({ value, setValue: (v) => value.set(v) })
99127 />
100128 <circle
101129 class=" transition-all duration-300 ease-in-out "
130 :class="circularBarClass()"
102131 cx="NaN"
103132 cy="NaN"
104133 r=""