@hanzogui/progress 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,223 @@
1
+ // forked from Radix UI
2
+ // https://github.com/radix-ui/primitives/blob/main/packages/react/progress/src/Progress.tsx
3
+
4
+ import type { GetProps } from '@hanzogui/core'
5
+ import { getVariableValue, isWeb, styled } from '@hanzogui/core'
6
+ import type { Scope } from '@hanzogui/create-context'
7
+ import { createContextScope } from '@hanzogui/create-context'
8
+ import { getSize } from '@hanzogui/get-token'
9
+ import { withStaticProperties } from '@hanzogui/helpers'
10
+ import { YStack } from '@hanzogui/stacks'
11
+ import { useState } from 'react'
12
+
13
+ const PROGRESS_NAME = 'Progress'
14
+
15
+ const [createProgressContext, createProgressScope] = createContextScope(PROGRESS_NAME)
16
+
17
+ type ProgressContextValue = {
18
+ value: number | null
19
+ max: number
20
+ width: number
21
+ }
22
+
23
+ const [ProgressProvider, useProgressContext] =
24
+ createProgressContext<ProgressContextValue>(PROGRESS_NAME)
25
+
26
+ /* -------------------------------------------------------------------------------------------------
27
+ * ProgressIndicator
28
+ * -----------------------------------------------------------------------------------------------*/
29
+
30
+ const INDICATOR_NAME = 'ProgressIndicator'
31
+
32
+ export const ProgressIndicatorFrame = styled(YStack, {
33
+ name: INDICATOR_NAME,
34
+
35
+ variants: {
36
+ unstyled: {
37
+ false: {
38
+ height: '100%',
39
+ width: '100%',
40
+ backgroundColor: '$background',
41
+ },
42
+ },
43
+ } as const,
44
+
45
+ defaultVariants: {
46
+ unstyled: process.env.HANZO_GUI_HEADLESS === '1',
47
+ },
48
+ })
49
+
50
+ export type ProgressIndicatorProps = GetProps<typeof ProgressIndicatorFrame>
51
+
52
+ const ProgressIndicator = ProgressIndicatorFrame.styleable(function ProgressIndicator(
53
+ props: ScopedProps<ProgressIndicatorProps>,
54
+ forwardedRef
55
+ ) {
56
+ const { __scopeProgress, transition, ...indicatorProps } = props
57
+ const context = useProgressContext(INDICATOR_NAME, __scopeProgress)
58
+
59
+ const progressRatio = (context.value ?? 0) / context.max
60
+
61
+ // indicator is 2x container width so bouncy animations can overshoot
62
+ // without visually extending past the right edge (parent has overflow:hidden)
63
+ // translateX percentage is relative to element's own width (200% of container)
64
+ // so we divide by 2 to get container-relative positioning:
65
+ // at 0%: x = -100% of element = -200% of container (fully hidden left)
66
+ // at 100%: x = -50% of element = -100% of container (right half visible)
67
+ let x: string | number
68
+ if (isWeb) {
69
+ // web: use percentage-based translateX for SSR-friendly rendering
70
+ // formula: -100% + (progressRatio * 50%) since translateX % is relative to element width
71
+ x = `${-100 + progressRatio * 50}%`
72
+ } else {
73
+ // native: use pixel-based transform (RN doesn't support percentage transforms reliably)
74
+ const baseWidth = context.width || 0
75
+ x = Math.ceil(-baseWidth * (2 - progressRatio))
76
+ }
77
+
78
+ return (
79
+ <ProgressIndicatorFrame
80
+ data-state={getProgressState(context.value, context.max)}
81
+ data-value={context.value ?? undefined}
82
+ data-max={context.max}
83
+ x={x}
84
+ width="200%"
85
+ {...(!props.unstyled && {
86
+ animateOnly: ['transform'],
87
+ // on native, hide until we have width measurement
88
+ ...(!isWeb && context.width === 0 && { opacity: 0 }),
89
+ })}
90
+ {...indicatorProps}
91
+ ref={forwardedRef}
92
+ transition={!isWeb && !context.width ? null : transition}
93
+ />
94
+ )
95
+ })
96
+
97
+ /* ---------------------------------------------------------------------------------------------- */
98
+
99
+ function defaultGetValueLabel(value: number, max: number) {
100
+ return `${Math.round((value / max) * 100)}%`
101
+ }
102
+
103
+ function getProgressState(
104
+ value: number | undefined | null,
105
+ maxValue: number
106
+ ): ProgressState {
107
+ return value == null ? 'indeterminate' : value === maxValue ? 'complete' : 'loading'
108
+ }
109
+
110
+ function isNumber(value: any): value is number {
111
+ return typeof value === 'number'
112
+ }
113
+
114
+ function isValidMaxNumber(max: any): max is number {
115
+ return isNumber(max) && !Number.isNaN(max) && max > 0
116
+ }
117
+
118
+ function isValidValueNumber(value: any, max: number): value is number {
119
+ return isNumber(value) && !Number.isNaN(value) && value <= max && value >= 0
120
+ }
121
+
122
+ /* -------------------------------------------------------------------------------------------------
123
+ * Progress
124
+ * -----------------------------------------------------------------------------------------------*/
125
+
126
+ const DEFAULT_MAX = 100
127
+
128
+ type ScopedProps<P> = P & { __scopeProgress?: Scope }
129
+
130
+ type ProgressState = 'indeterminate' | 'complete' | 'loading'
131
+
132
+ export const ProgressFrame = styled(YStack, {
133
+ name: 'Progress',
134
+
135
+ variants: {
136
+ unstyled: {
137
+ false: {
138
+ borderRadius: 100_000,
139
+ overflow: 'hidden',
140
+ backgroundColor: '$background',
141
+ },
142
+ },
143
+
144
+ size: {
145
+ '...size': (val) => {
146
+ const size = Math.round(getVariableValue(getSize(val)) * 0.25)
147
+ return {
148
+ height: size,
149
+ minWidth: getVariableValue(size) * 20,
150
+ width: '100%',
151
+ }
152
+ },
153
+ },
154
+ } as const,
155
+
156
+ defaultVariants: {
157
+ unstyled: process.env.HANZO_GUI_HEADLESS === '1',
158
+ },
159
+ })
160
+
161
+ export interface ProgressExtraProps {
162
+ value?: number | null | undefined
163
+ max?: number
164
+ getValueLabel?(value: number, max: number): string
165
+ }
166
+
167
+ export type ProgressProps = GetProps<typeof ProgressFrame> & ProgressExtraProps
168
+
169
+ const Progress = withStaticProperties(
170
+ ProgressFrame.styleable<ProgressExtraProps>(function Progress(props, forwardedRef) {
171
+ const {
172
+ // @ts-expect-error
173
+ __scopeProgress,
174
+ value: valueProp,
175
+ max: maxProp,
176
+ getValueLabel = defaultGetValueLabel,
177
+ size = '$true',
178
+ ...progressProps
179
+ } = props
180
+
181
+ const max = isValidMaxNumber(maxProp) ? maxProp : DEFAULT_MAX
182
+ const value = isValidValueNumber(valueProp, max) ? Math.round(valueProp) : null
183
+ const valueLabel = isNumber(value) ? getValueLabel(value, max) : undefined
184
+
185
+ // only needed for native where we can't use percentage-based transforms
186
+ const [width, setWidth] = useState(0)
187
+
188
+ return (
189
+ <ProgressProvider scope={__scopeProgress} value={value} max={max} width={width}>
190
+ <ProgressFrame
191
+ aria-valuemax={max}
192
+ aria-valuemin={0}
193
+ aria-valuenow={isNumber(value) ? value : undefined}
194
+ aria-valuetext={valueLabel}
195
+ // @ts-ignore
196
+ role="progressbar"
197
+ data-state={getProgressState(value, max)}
198
+ data-value={value ?? undefined}
199
+ data-max={max}
200
+ {...(progressProps.unstyled !== true && {
201
+ size,
202
+ })}
203
+ {...progressProps}
204
+ {...(!isWeb && {
205
+ onLayout: (e) => {
206
+ const newWidth = Math.round(e.nativeEvent.layout.width)
207
+ if (newWidth !== width) {
208
+ setWidth(newWidth)
209
+ }
210
+ progressProps.onLayout?.(e)
211
+ },
212
+ })}
213
+ ref={forwardedRef}
214
+ />
215
+ </ProgressProvider>
216
+ )
217
+ }),
218
+ {
219
+ Indicator: ProgressIndicator,
220
+ }
221
+ )
222
+
223
+ export { createProgressScope, Progress, ProgressIndicator }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './Progress'
@@ -0,0 +1,69 @@
1
+ import type { GetProps } from '@gui/core';
2
+ declare const createProgressScope: import("@gui/create-context").CreateScope;
3
+ export declare const ProgressIndicatorFrame: import("@gui/core").GuiComponent<import("@gui/core").TamaDefer, import("@gui/core").GuiElement, import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
4
+ unstyled?: boolean | undefined;
5
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
6
+ fullscreen?: boolean | undefined;
7
+ }, import("@gui/core").StaticConfigPublic>;
8
+ export type ProgressIndicatorProps = GetProps<typeof ProgressIndicatorFrame>;
9
+ declare const ProgressIndicator: import("@gui/core").GuiComponent<import("@gui/core").GetFinalProps<import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
10
+ unstyled?: boolean | undefined;
11
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
12
+ fullscreen?: boolean | undefined;
13
+ }>, import("@gui/core").GuiElement, import("@gui/core").RNGuiViewNonStyleProps & void, import("@gui/core").StackStyleBase, {
14
+ unstyled?: boolean | undefined;
15
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
16
+ fullscreen?: boolean | undefined;
17
+ }, import("@gui/core").StaticConfigPublic>;
18
+ export declare const ProgressFrame: import("@gui/core").GuiComponent<import("@gui/core").TamaDefer, import("@gui/core").GuiElement, import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
19
+ unstyled?: boolean | undefined;
20
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
21
+ fullscreen?: boolean | undefined;
22
+ size?: import("@gui/core").SizeTokens | undefined;
23
+ }, import("@gui/core").StaticConfigPublic>;
24
+ export interface ProgressExtraProps {
25
+ value?: number | null | undefined;
26
+ max?: number;
27
+ getValueLabel?(value: number, max: number): string;
28
+ }
29
+ export type ProgressProps = GetProps<typeof ProgressFrame> & ProgressExtraProps;
30
+ declare const Progress: import("react").ForwardRefExoticComponent<Omit<import("@gui/core").GetFinalProps<import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
31
+ unstyled?: boolean | undefined;
32
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
33
+ fullscreen?: boolean | undefined;
34
+ size?: import("@gui/core").SizeTokens | undefined;
35
+ }>, keyof ProgressExtraProps> & ProgressExtraProps & import("react").RefAttributes<import("@gui/core").GuiElement>> & import("@gui/core").StaticComponentObject<Omit<import("@gui/core").GetFinalProps<import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
36
+ unstyled?: boolean | undefined;
37
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
38
+ fullscreen?: boolean | undefined;
39
+ size?: import("@gui/core").SizeTokens | undefined;
40
+ }>, keyof ProgressExtraProps> & ProgressExtraProps, import("@gui/core").GuiElement, import("@gui/core").RNGuiViewNonStyleProps & ProgressExtraProps, import("@gui/core").StackStyleBase, {
41
+ unstyled?: boolean | undefined;
42
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
43
+ fullscreen?: boolean | undefined;
44
+ size?: import("@gui/core").SizeTokens | undefined;
45
+ }, import("@gui/core").StaticConfigPublic> & Omit<import("@gui/core").StaticConfigPublic, "staticConfig" | "styleable"> & {
46
+ __tama: [Omit<import("@gui/core").GetFinalProps<import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
47
+ unstyled?: boolean | undefined;
48
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
49
+ fullscreen?: boolean | undefined;
50
+ size?: import("@gui/core").SizeTokens | undefined;
51
+ }>, keyof ProgressExtraProps> & ProgressExtraProps, import("@gui/core").GuiElement, import("@gui/core").RNGuiViewNonStyleProps & ProgressExtraProps, import("@gui/core").StackStyleBase, {
52
+ unstyled?: boolean | undefined;
53
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
54
+ fullscreen?: boolean | undefined;
55
+ size?: import("@gui/core").SizeTokens | undefined;
56
+ }, import("@gui/core").StaticConfigPublic];
57
+ } & {
58
+ Indicator: import("@gui/core").GuiComponent<import("@gui/core").GetFinalProps<import("@gui/core").RNGuiViewNonStyleProps, import("@gui/core").StackStyleBase, {
59
+ unstyled?: boolean | undefined;
60
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
61
+ fullscreen?: boolean | undefined;
62
+ }>, import("@gui/core").GuiElement, import("@gui/core").RNGuiViewNonStyleProps & void, import("@gui/core").StackStyleBase, {
63
+ unstyled?: boolean | undefined;
64
+ elevation?: number | import("@gui/core").SizeTokens | undefined;
65
+ fullscreen?: boolean | undefined;
66
+ }, import("@gui/core").StaticConfigPublic>;
67
+ };
68
+ export { createProgressScope, Progress, ProgressIndicator };
69
+ //# sourceMappingURL=Progress.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from './Progress';
2
+ //# sourceMappingURL=index.d.ts.map