@lattice-ui/progress 0.3.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.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @lattice-ui/progress
2
+
3
+ Headless progress and spinner primitives for Roblox UI.
4
+
5
+ ## Exports
6
+
7
+ - `Progress`
8
+ - `Progress.Root`
9
+ - `Progress.Indicator`
10
+ - `Progress.Spinner`
11
+
12
+ ## Notes
13
+
14
+ - `Progress` supports `value`/`max` and clamps out-of-range values.
15
+ - `indeterminate` mode uses a fixed indicator ratio.
16
+ - `Spinner` defaults to `spinning=true` and rotates on heartbeat.
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ProgressIndicatorProps } from "./types";
3
+ export declare function ProgressIndicator(props: ProgressIndicatorProps): React.JSX.Element;
@@ -0,0 +1,30 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local useProgressContext = TS.import(script, script.Parent, "context").useProgressContext
7
+ local function ProgressIndicator(props)
8
+ local progressContext = useProgressContext()
9
+ local widthScale = if progressContext.indeterminate then 0.35 else progressContext.ratio
10
+ local xScale = if progressContext.indeterminate then 0 else 0
11
+ if props.asChild then
12
+ local child = props.children
13
+ if not child then
14
+ error("[ProgressIndicator] `asChild` requires a child element.")
15
+ end
16
+ return React.createElement(Slot, {
17
+ Position = UDim2.fromScale(xScale, 0),
18
+ Size = UDim2.fromScale(widthScale, 1),
19
+ }, child)
20
+ end
21
+ return React.createElement("frame", {
22
+ BackgroundColor3 = Color3.fromRGB(102, 156, 255),
23
+ BorderSizePixel = 0,
24
+ Position = UDim2.fromScale(xScale, 0),
25
+ Size = UDim2.fromScale(widthScale, 1),
26
+ }, props.children)
27
+ end
28
+ return {
29
+ ProgressIndicator = ProgressIndicator,
30
+ }
@@ -0,0 +1,4 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { ProgressProps } from "./types";
3
+ export declare function ProgressRoot(props: ProgressProps): React.JSX.Element;
4
+ export { ProgressRoot as Progress };
@@ -0,0 +1,46 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local useControllableState = _core.useControllableState
6
+ local ProgressContextProvider = TS.import(script, script.Parent, "context").ProgressContextProvider
7
+ local _math = TS.import(script, script.Parent, "math")
8
+ local clampProgressValue = _math.clampProgressValue
9
+ local resolveProgressRatio = _math.resolveProgressRatio
10
+ local function ProgressRoot(props)
11
+ local _condition = props.max
12
+ if _condition == nil then
13
+ _condition = 100
14
+ end
15
+ local max = math.max(1, _condition)
16
+ local indeterminate = props.indeterminate == true
17
+ local _object = {
18
+ value = props.value,
19
+ }
20
+ local _left = "defaultValue"
21
+ local _condition_1 = props.defaultValue
22
+ if _condition_1 == nil then
23
+ _condition_1 = 0
24
+ end
25
+ _object[_left] = _condition_1
26
+ _object.onChange = props.onValueChange
27
+ local _binding = useControllableState(_object)
28
+ local value = _binding[1]
29
+ local clampedValue = clampProgressValue(value, max)
30
+ local ratio = resolveProgressRatio(clampedValue, max, indeterminate)
31
+ local contextValue = React.useMemo(function()
32
+ return {
33
+ value = clampedValue,
34
+ max = max,
35
+ ratio = ratio,
36
+ indeterminate = indeterminate,
37
+ }
38
+ end, { clampedValue, indeterminate, max, ratio })
39
+ return React.createElement(ProgressContextProvider, {
40
+ value = contextValue,
41
+ }, props.children)
42
+ end
43
+ return {
44
+ ProgressRoot = ProgressRoot,
45
+ Progress = ProgressRoot,
46
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { SpinnerProps } from "./types";
3
+ export declare function Spinner(props: SpinnerProps): React.JSX.Element;
@@ -0,0 +1,68 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local RunService = game:GetService("RunService")
7
+ local function toGuiObject(instance)
8
+ if not instance or not instance:IsA("GuiObject") then
9
+ return nil
10
+ end
11
+ return instance
12
+ end
13
+ local function Spinner(props)
14
+ local _condition = props.spinning
15
+ if _condition == nil then
16
+ _condition = true
17
+ end
18
+ local spinning = _condition
19
+ local _condition_1 = props.speedDegPerSecond
20
+ if _condition_1 == nil then
21
+ _condition_1 = 180
22
+ end
23
+ local speedDegPerSecond = _condition_1
24
+ local spinnerRef = React.useRef()
25
+ local setSpinnerRef = React.useCallback(function(instance)
26
+ spinnerRef.current = toGuiObject(instance)
27
+ end, {})
28
+ React.useEffect(function()
29
+ if not spinning then
30
+ return nil
31
+ end
32
+ local connection = RunService.Heartbeat:Connect(function(deltaSeconds)
33
+ local spinner = spinnerRef.current
34
+ if not spinner then
35
+ return nil
36
+ end
37
+ spinner.Rotation += speedDegPerSecond * deltaSeconds
38
+ end)
39
+ return function()
40
+ connection:Disconnect()
41
+ end
42
+ end, { spinning, speedDegPerSecond })
43
+ if props.asChild then
44
+ local child = props.children
45
+ if not child then
46
+ error("[Spinner] `asChild` requires a child element.")
47
+ end
48
+ return React.createElement(Slot, {
49
+ Visible = spinning,
50
+ ref = setSpinnerRef,
51
+ }, child)
52
+ end
53
+ return React.createElement("frame", {
54
+ BackgroundTransparency = 1,
55
+ BorderSizePixel = 0,
56
+ Size = UDim2.fromOffset(22, 22),
57
+ Visible = spinning,
58
+ ref = setSpinnerRef,
59
+ }, React.createElement("uicorner", {
60
+ CornerRadius = UDim.new(1, 0),
61
+ }), React.createElement("uistroke", {
62
+ Color = Color3.fromRGB(102, 156, 255),
63
+ Thickness = 2,
64
+ }))
65
+ end
66
+ return {
67
+ Spinner = Spinner,
68
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProgressContextValue } from "./types";
2
+ declare const ProgressContextProvider: import("@rbxts/react").Provider<ProgressContextValue | undefined>, useProgressContext: () => ProgressContextValue;
3
+ export { ProgressContextProvider, useProgressContext };
@@ -0,0 +1,10 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local createStrictContext = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out).createStrictContext
4
+ local _binding = createStrictContext("Progress")
5
+ local ProgressContextProvider = _binding[1]
6
+ local useProgressContext = _binding[2]
7
+ return {
8
+ ProgressContextProvider = ProgressContextProvider,
9
+ useProgressContext = useProgressContext,
10
+ }
@@ -0,0 +1,2 @@
1
+ export declare function clampProgressValue(value: number, max: number): number;
2
+ export declare function resolveProgressRatio(value: number, max: number, indeterminate?: boolean): number;
@@ -0,0 +1,17 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local function clampProgressValue(value, max)
3
+ local safeMax = math.max(1, max)
4
+ return math.clamp(value, 0, safeMax)
5
+ end
6
+ local function resolveProgressRatio(value, max, indeterminate)
7
+ if indeterminate == true then
8
+ return 0.25
9
+ end
10
+ local safeMax = math.max(1, max)
11
+ local clamped = clampProgressValue(value, safeMax)
12
+ return clamped / safeMax
13
+ end
14
+ return {
15
+ clampProgressValue = clampProgressValue,
16
+ resolveProgressRatio = resolveProgressRatio,
17
+ }
@@ -0,0 +1,25 @@
1
+ import type React from "@rbxts/react";
2
+ export type ProgressContextValue = {
3
+ value: number;
4
+ max: number;
5
+ ratio: number;
6
+ indeterminate: boolean;
7
+ };
8
+ export type ProgressProps = {
9
+ value?: number;
10
+ defaultValue?: number;
11
+ onValueChange?: (value: number) => void;
12
+ max?: number;
13
+ indeterminate?: boolean;
14
+ children?: React.ReactNode;
15
+ };
16
+ export type ProgressIndicatorProps = {
17
+ asChild?: boolean;
18
+ children?: React.ReactElement;
19
+ };
20
+ export type SpinnerProps = {
21
+ asChild?: boolean;
22
+ spinning?: boolean;
23
+ speedDegPerSecond?: number;
24
+ children?: React.ReactElement;
25
+ };
@@ -0,0 +1,2 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ return nil
package/out/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { ProgressIndicator } from "./Progress/ProgressIndicator";
2
+ import { ProgressRoot } from "./Progress/ProgressRoot";
3
+ import { Spinner } from "./Progress/Spinner";
4
+ export declare const Progress: {
5
+ readonly Root: typeof ProgressRoot;
6
+ readonly Indicator: typeof ProgressIndicator;
7
+ readonly Spinner: typeof Spinner;
8
+ };
9
+ export { clampProgressValue, resolveProgressRatio } from "./Progress/math";
10
+ export type { ProgressContextValue, ProgressIndicatorProps, ProgressProps, SpinnerProps } from "./Progress/types";
package/out/init.luau ADDED
@@ -0,0 +1,16 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ local ProgressIndicator = TS.import(script, script, "Progress", "ProgressIndicator").ProgressIndicator
5
+ local ProgressRoot = TS.import(script, script, "Progress", "ProgressRoot").ProgressRoot
6
+ local Spinner = TS.import(script, script, "Progress", "Spinner").Spinner
7
+ local Progress = {
8
+ Root = ProgressRoot,
9
+ Indicator = ProgressIndicator,
10
+ Spinner = Spinner,
11
+ }
12
+ local _math = TS.import(script, script, "Progress", "math")
13
+ exports.clampProgressValue = _math.clampProgressValue
14
+ exports.resolveProgressRatio = _math.resolveProgressRatio
15
+ exports.Progress = Progress
16
+ return exports
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@lattice-ui/progress",
3
+ "version": "0.3.0",
4
+ "private": false,
5
+ "main": "out/init.luau",
6
+ "types": "out/index.d.ts",
7
+ "dependencies": {
8
+ "@lattice-ui/core": "0.3.0"
9
+ },
10
+ "devDependencies": {
11
+ "@rbxts/react": "17.3.7-ts.1",
12
+ "@rbxts/react-roblox": "17.3.7-ts.1"
13
+ },
14
+ "peerDependencies": {
15
+ "@rbxts/react": "^17",
16
+ "@rbxts/react-roblox": "^17"
17
+ },
18
+ "scripts": {
19
+ "build": "rbxtsc -p tsconfig.json",
20
+ "typecheck": "tsc -p tsconfig.typecheck.json",
21
+ "watch": "rbxtsc -p tsconfig.json -w"
22
+ }
23
+ }
@@ -0,0 +1,34 @@
1
+ import { React, Slot } from "@lattice-ui/core";
2
+ import { useProgressContext } from "./context";
3
+ import type { ProgressIndicatorProps } from "./types";
4
+
5
+ export function ProgressIndicator(props: ProgressIndicatorProps) {
6
+ const progressContext = useProgressContext();
7
+
8
+ const widthScale = progressContext.indeterminate ? 0.35 : progressContext.ratio;
9
+ const xScale = progressContext.indeterminate ? 0 : 0;
10
+
11
+ if (props.asChild) {
12
+ const child = props.children;
13
+ if (!child) {
14
+ error("[ProgressIndicator] `asChild` requires a child element.");
15
+ }
16
+
17
+ return (
18
+ <Slot Position={UDim2.fromScale(xScale, 0)} Size={UDim2.fromScale(widthScale, 1)}>
19
+ {child}
20
+ </Slot>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <frame
26
+ BackgroundColor3={Color3.fromRGB(102, 156, 255)}
27
+ BorderSizePixel={0}
28
+ Position={UDim2.fromScale(xScale, 0)}
29
+ Size={UDim2.fromScale(widthScale, 1)}
30
+ >
31
+ {props.children}
32
+ </frame>
33
+ );
34
+ }
@@ -0,0 +1,32 @@
1
+ import { React, useControllableState } from "@lattice-ui/core";
2
+ import { ProgressContextProvider } from "./context";
3
+ import { clampProgressValue, resolveProgressRatio } from "./math";
4
+ import type { ProgressProps } from "./types";
5
+
6
+ export function ProgressRoot(props: ProgressProps) {
7
+ const max = math.max(1, props.max ?? 100);
8
+ const indeterminate = props.indeterminate === true;
9
+
10
+ const [value] = useControllableState<number>({
11
+ value: props.value,
12
+ defaultValue: props.defaultValue ?? 0,
13
+ onChange: props.onValueChange,
14
+ });
15
+
16
+ const clampedValue = clampProgressValue(value, max);
17
+ const ratio = resolveProgressRatio(clampedValue, max, indeterminate);
18
+
19
+ const contextValue = React.useMemo(
20
+ () => ({
21
+ value: clampedValue,
22
+ max,
23
+ ratio,
24
+ indeterminate,
25
+ }),
26
+ [clampedValue, indeterminate, max, ratio],
27
+ );
28
+
29
+ return <ProgressContextProvider value={contextValue}>{props.children}</ProgressContextProvider>;
30
+ }
31
+
32
+ export { ProgressRoot as Progress };
@@ -0,0 +1,68 @@
1
+ import { React, Slot } from "@lattice-ui/core";
2
+ import type { SpinnerProps } from "./types";
3
+
4
+ const RunService = game.GetService("RunService");
5
+
6
+ function toGuiObject(instance: Instance | undefined) {
7
+ if (!instance || !instance.IsA("GuiObject")) {
8
+ return undefined;
9
+ }
10
+
11
+ return instance;
12
+ }
13
+
14
+ export function Spinner(props: SpinnerProps) {
15
+ const spinning = props.spinning ?? true;
16
+ const speedDegPerSecond = props.speedDegPerSecond ?? 180;
17
+
18
+ const spinnerRef = React.useRef<GuiObject>();
19
+
20
+ const setSpinnerRef = React.useCallback((instance: Instance | undefined) => {
21
+ spinnerRef.current = toGuiObject(instance);
22
+ }, []);
23
+
24
+ React.useEffect(() => {
25
+ if (!spinning) {
26
+ return;
27
+ }
28
+
29
+ const connection = RunService.Heartbeat.Connect((deltaSeconds) => {
30
+ const spinner = spinnerRef.current;
31
+ if (!spinner) {
32
+ return;
33
+ }
34
+
35
+ spinner.Rotation += speedDegPerSecond * deltaSeconds;
36
+ });
37
+
38
+ return () => {
39
+ connection.Disconnect();
40
+ };
41
+ }, [spinning, speedDegPerSecond]);
42
+
43
+ if (props.asChild) {
44
+ const child = props.children;
45
+ if (!child) {
46
+ error("[Spinner] `asChild` requires a child element.");
47
+ }
48
+
49
+ return (
50
+ <Slot Visible={spinning} ref={setSpinnerRef}>
51
+ {child}
52
+ </Slot>
53
+ );
54
+ }
55
+
56
+ return (
57
+ <frame
58
+ BackgroundTransparency={1}
59
+ BorderSizePixel={0}
60
+ Size={UDim2.fromOffset(22, 22)}
61
+ Visible={spinning}
62
+ ref={setSpinnerRef}
63
+ >
64
+ <uicorner CornerRadius={new UDim(1, 0)} />
65
+ <uistroke Color={Color3.fromRGB(102, 156, 255)} Thickness={2} />
66
+ </frame>
67
+ );
68
+ }
@@ -0,0 +1,6 @@
1
+ import { createStrictContext } from "@lattice-ui/core";
2
+ import type { ProgressContextValue } from "./types";
3
+
4
+ const [ProgressContextProvider, useProgressContext] = createStrictContext<ProgressContextValue>("Progress");
5
+
6
+ export { ProgressContextProvider, useProgressContext };
@@ -0,0 +1,14 @@
1
+ export function clampProgressValue(value: number, max: number) {
2
+ const safeMax = math.max(1, max);
3
+ return math.clamp(value, 0, safeMax);
4
+ }
5
+
6
+ export function resolveProgressRatio(value: number, max: number, indeterminate?: boolean) {
7
+ if (indeterminate === true) {
8
+ return 0.25;
9
+ }
10
+
11
+ const safeMax = math.max(1, max);
12
+ const clamped = clampProgressValue(value, safeMax);
13
+ return clamped / safeMax;
14
+ }
@@ -0,0 +1,29 @@
1
+ import type React from "@rbxts/react";
2
+
3
+ export type ProgressContextValue = {
4
+ value: number;
5
+ max: number;
6
+ ratio: number;
7
+ indeterminate: boolean;
8
+ };
9
+
10
+ export type ProgressProps = {
11
+ value?: number;
12
+ defaultValue?: number;
13
+ onValueChange?: (value: number) => void;
14
+ max?: number;
15
+ indeterminate?: boolean;
16
+ children?: React.ReactNode;
17
+ };
18
+
19
+ export type ProgressIndicatorProps = {
20
+ asChild?: boolean;
21
+ children?: React.ReactElement;
22
+ };
23
+
24
+ export type SpinnerProps = {
25
+ asChild?: boolean;
26
+ spinning?: boolean;
27
+ speedDegPerSecond?: number;
28
+ children?: React.ReactElement;
29
+ };
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { ProgressIndicator } from "./Progress/ProgressIndicator";
2
+ import { ProgressRoot } from "./Progress/ProgressRoot";
3
+ import { Spinner } from "./Progress/Spinner";
4
+
5
+ export const Progress = {
6
+ Root: ProgressRoot,
7
+ Indicator: ProgressIndicator,
8
+ Spinner,
9
+ } as const;
10
+
11
+ export { clampProgressValue, resolveProgressRatio } from "./Progress/math";
12
+ export type { ProgressContextValue, ProgressIndicatorProps, ProgressProps, SpinnerProps } from "./Progress/types";
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "out",
6
+ "declaration": true,
7
+ "typeRoots": [
8
+ "./node_modules/@rbxts",
9
+ "../../node_modules/@rbxts",
10
+ "./node_modules/@lattice-ui",
11
+ "../../node_modules/@lattice-ui"
12
+ ],
13
+ "types": ["types", "compiler-types"]
14
+ },
15
+ "include": ["src"]
16
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "baseUrl": "..",
6
+ "rootDir": "..",
7
+ "paths": {
8
+ "@lattice-ui/accordion": ["accordion/src/index.ts"],
9
+ "@lattice-ui/avatar": ["avatar/src/index.ts"],
10
+ "@lattice-ui/checkbox": ["checkbox/src/index.ts"],
11
+ "@lattice-ui/combobox": ["combobox/src/index.ts"],
12
+ "@lattice-ui/core": ["core/src/index.ts"],
13
+ "@lattice-ui/dialog": ["dialog/src/index.ts"],
14
+ "@lattice-ui/focus": ["focus/src/index.ts"],
15
+ "@lattice-ui/layer": ["layer/src/index.ts"],
16
+ "@lattice-ui/menu": ["menu/src/index.ts"],
17
+ "@lattice-ui/popover": ["popover/src/index.ts"],
18
+ "@lattice-ui/popper": ["popper/src/index.ts"],
19
+ "@lattice-ui/progress": ["progress/src/index.ts"],
20
+ "@lattice-ui/radio-group": ["radio-group/src/index.ts"],
21
+ "@lattice-ui/scroll-area": ["scroll-area/src/index.ts"],
22
+ "@lattice-ui/select": ["select/src/index.ts"],
23
+ "@lattice-ui/slider": ["slider/src/index.ts"],
24
+ "@lattice-ui/style": ["style/src/index.ts"],
25
+ "@lattice-ui/switch": ["switch/src/index.ts"],
26
+ "@lattice-ui/system": ["system/src/index.ts"],
27
+ "@lattice-ui/tabs": ["tabs/src/index.ts"],
28
+ "@lattice-ui/text-field": ["text-field/src/index.ts"],
29
+ "@lattice-ui/textarea": ["textarea/src/index.ts"],
30
+ "@lattice-ui/toast": ["toast/src/index.ts"],
31
+ "@lattice-ui/toggle-group": ["toggle-group/src/index.ts"],
32
+ "@lattice-ui/tooltip": ["tooltip/src/index.ts"]
33
+ }
34
+ }
35
+ }