@lattice-ui/avatar 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 +16 -0
- package/out/Avatar/AvatarFallback.d.ts +3 -0
- package/out/Avatar/AvatarFallback.luau +34 -0
- package/out/Avatar/AvatarImage.d.ts +3 -0
- package/out/Avatar/AvatarImage.luau +81 -0
- package/out/Avatar/AvatarRoot.d.ts +4 -0
- package/out/Avatar/AvatarRoot.luau +48 -0
- package/out/Avatar/context.d.ts +3 -0
- package/out/Avatar/context.luau +10 -0
- package/out/Avatar/state.d.ts +2 -0
- package/out/Avatar/state.luau +13 -0
- package/out/Avatar/types.d.ts +22 -0
- package/out/Avatar/types.luau +2 -0
- package/out/index.d.ts +11 -0
- package/out/init.luau +14 -0
- package/package.json +23 -0
- package/src/Avatar/AvatarFallback.tsx +33 -0
- package/src/Avatar/AvatarImage.tsx +78 -0
- package/src/Avatar/AvatarRoot.tsx +50 -0
- package/src/Avatar/context.ts +6 -0
- package/src/Avatar/state.ts +13 -0
- package/src/Avatar/types.ts +26 -0
- package/src/index.ts +13 -0
- package/tsconfig.json +16 -0
- package/tsconfig.typecheck.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @lattice-ui/avatar
|
|
2
|
+
|
|
3
|
+
Headless avatar primitives for Roblox UI with delayed fallback rendering.
|
|
4
|
+
|
|
5
|
+
## Exports
|
|
6
|
+
|
|
7
|
+
- `Avatar`
|
|
8
|
+
- `Avatar.Root`
|
|
9
|
+
- `Avatar.Image`
|
|
10
|
+
- `Avatar.Fallback`
|
|
11
|
+
|
|
12
|
+
## Notes
|
|
13
|
+
|
|
14
|
+
- `delayMs` controls fallback reveal timing (default: `250`).
|
|
15
|
+
- Missing image sources immediately show fallback content.
|
|
16
|
+
- Fallback visibility logic is exported for unit testing.
|
|
@@ -0,0 +1,34 @@
|
|
|
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 useAvatarContext = TS.import(script, script.Parent, "context").useAvatarContext
|
|
7
|
+
local resolveAvatarFallbackVisible = TS.import(script, script.Parent, "state").resolveAvatarFallbackVisible
|
|
8
|
+
local function AvatarFallback(props)
|
|
9
|
+
local avatarContext = useAvatarContext()
|
|
10
|
+
local visible = resolveAvatarFallbackVisible(avatarContext.status, avatarContext.delayElapsed)
|
|
11
|
+
if props.asChild then
|
|
12
|
+
local child = props.children
|
|
13
|
+
if not child then
|
|
14
|
+
error("[AvatarFallback] `asChild` requires a child element.")
|
|
15
|
+
end
|
|
16
|
+
return React.createElement(Slot, {
|
|
17
|
+
Visible = visible,
|
|
18
|
+
}, child)
|
|
19
|
+
end
|
|
20
|
+
return React.createElement("textlabel", {
|
|
21
|
+
BackgroundColor3 = Color3.fromRGB(65, 72, 89),
|
|
22
|
+
BorderSizePixel = 0,
|
|
23
|
+
Size = UDim2.fromOffset(40, 40),
|
|
24
|
+
Text = "AB",
|
|
25
|
+
TextColor3 = Color3.fromRGB(235, 240, 248),
|
|
26
|
+
TextSize = 14,
|
|
27
|
+
Visible = visible,
|
|
28
|
+
}, React.createElement("uicorner", {
|
|
29
|
+
CornerRadius = UDim.new(1, 0),
|
|
30
|
+
}), props.children)
|
|
31
|
+
end
|
|
32
|
+
return {
|
|
33
|
+
AvatarFallback = AvatarFallback,
|
|
34
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 useAvatarContext = TS.import(script, script.Parent, "context").useAvatarContext
|
|
7
|
+
local function toImageLabel(instance)
|
|
8
|
+
if not instance or not instance:IsA("ImageLabel") then
|
|
9
|
+
return nil
|
|
10
|
+
end
|
|
11
|
+
return instance
|
|
12
|
+
end
|
|
13
|
+
local function AvatarImage(props)
|
|
14
|
+
local avatarContext = useAvatarContext()
|
|
15
|
+
local _condition = props.src
|
|
16
|
+
if _condition == nil then
|
|
17
|
+
_condition = avatarContext.src
|
|
18
|
+
end
|
|
19
|
+
local source = _condition
|
|
20
|
+
local imageRef = React.useRef()
|
|
21
|
+
local setImageRef = React.useCallback(function(instance)
|
|
22
|
+
imageRef.current = toImageLabel(instance)
|
|
23
|
+
end, {})
|
|
24
|
+
React.useEffect(function()
|
|
25
|
+
if source == nil or #source == 0 then
|
|
26
|
+
avatarContext.setStatus("error")
|
|
27
|
+
return nil
|
|
28
|
+
end
|
|
29
|
+
avatarContext.setStatus("loading")
|
|
30
|
+
end, { avatarContext, source })
|
|
31
|
+
React.useEffect(function()
|
|
32
|
+
local image = imageRef.current
|
|
33
|
+
if not image then
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
36
|
+
if image.IsLoaded then
|
|
37
|
+
avatarContext.setStatus("loaded")
|
|
38
|
+
end
|
|
39
|
+
local connection = image:GetPropertyChangedSignal("IsLoaded"):Connect(function()
|
|
40
|
+
if image.IsLoaded then
|
|
41
|
+
avatarContext.setStatus("loaded")
|
|
42
|
+
end
|
|
43
|
+
end)
|
|
44
|
+
return function()
|
|
45
|
+
connection:Disconnect()
|
|
46
|
+
end
|
|
47
|
+
end, { avatarContext, source })
|
|
48
|
+
if props.asChild then
|
|
49
|
+
local child = props.children
|
|
50
|
+
if not child then
|
|
51
|
+
error("[AvatarImage] `asChild` requires a child element.")
|
|
52
|
+
end
|
|
53
|
+
local _attributes = {}
|
|
54
|
+
local _condition_1 = source
|
|
55
|
+
if _condition_1 == nil then
|
|
56
|
+
_condition_1 = ""
|
|
57
|
+
end
|
|
58
|
+
_attributes.Image = _condition_1
|
|
59
|
+
_attributes.Visible = avatarContext.status == "loaded"
|
|
60
|
+
_attributes.ref = setImageRef
|
|
61
|
+
return React.createElement(Slot, _attributes, child)
|
|
62
|
+
end
|
|
63
|
+
local _attributes = {
|
|
64
|
+
BackgroundTransparency = 1,
|
|
65
|
+
BorderSizePixel = 0,
|
|
66
|
+
}
|
|
67
|
+
local _condition_1 = source
|
|
68
|
+
if _condition_1 == nil then
|
|
69
|
+
_condition_1 = ""
|
|
70
|
+
end
|
|
71
|
+
_attributes.Image = _condition_1
|
|
72
|
+
_attributes.Size = UDim2.fromOffset(40, 40)
|
|
73
|
+
_attributes.Visible = avatarContext.status == "loaded"
|
|
74
|
+
_attributes.ref = setImageRef
|
|
75
|
+
return React.createElement("imagelabel", _attributes, React.createElement("uicorner", {
|
|
76
|
+
CornerRadius = UDim.new(1, 0),
|
|
77
|
+
}))
|
|
78
|
+
end
|
|
79
|
+
return {
|
|
80
|
+
AvatarImage = AvatarImage,
|
|
81
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local React = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out).React
|
|
4
|
+
local AvatarContextProvider = TS.import(script, script.Parent, "context").AvatarContextProvider
|
|
5
|
+
local function AvatarRoot(props)
|
|
6
|
+
local _condition = props.delayMs
|
|
7
|
+
if _condition == nil then
|
|
8
|
+
_condition = 250
|
|
9
|
+
end
|
|
10
|
+
local delayMs = math.max(0, _condition)
|
|
11
|
+
local hasSource = props.src ~= nil and #props.src > 0
|
|
12
|
+
local status, setStatus = React.useState(if hasSource then "loading" else "error")
|
|
13
|
+
local delayElapsed, setDelayElapsed = React.useState(not hasSource)
|
|
14
|
+
local sequenceRef = React.useRef(0)
|
|
15
|
+
React.useEffect(function()
|
|
16
|
+
sequenceRef.current += 1
|
|
17
|
+
local sequence = sequenceRef.current
|
|
18
|
+
if props.src == nil or #props.src == 0 then
|
|
19
|
+
setStatus("error")
|
|
20
|
+
setDelayElapsed(true)
|
|
21
|
+
return nil
|
|
22
|
+
end
|
|
23
|
+
setStatus("loading")
|
|
24
|
+
setDelayElapsed(false)
|
|
25
|
+
local delaySeconds = delayMs / 1000
|
|
26
|
+
task.delay(delaySeconds, function()
|
|
27
|
+
if sequenceRef.current ~= sequence then
|
|
28
|
+
return nil
|
|
29
|
+
end
|
|
30
|
+
setDelayElapsed(true)
|
|
31
|
+
end)
|
|
32
|
+
end, { delayMs, props.src })
|
|
33
|
+
local contextValue = React.useMemo(function()
|
|
34
|
+
return {
|
|
35
|
+
src = props.src,
|
|
36
|
+
status = status,
|
|
37
|
+
setStatus = setStatus,
|
|
38
|
+
delayElapsed = delayElapsed,
|
|
39
|
+
}
|
|
40
|
+
end, { delayElapsed, props.src, status })
|
|
41
|
+
return React.createElement(AvatarContextProvider, {
|
|
42
|
+
value = contextValue,
|
|
43
|
+
}, props.children)
|
|
44
|
+
end
|
|
45
|
+
return {
|
|
46
|
+
AvatarRoot = AvatarRoot,
|
|
47
|
+
Avatar = AvatarRoot,
|
|
48
|
+
}
|
|
@@ -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("Avatar")
|
|
5
|
+
local AvatarContextProvider = _binding[1]
|
|
6
|
+
local useAvatarContext = _binding[2]
|
|
7
|
+
return {
|
|
8
|
+
AvatarContextProvider = AvatarContextProvider,
|
|
9
|
+
useAvatarContext = useAvatarContext,
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local function resolveAvatarFallbackVisible(status, delayElapsed)
|
|
3
|
+
if status == "loaded" then
|
|
4
|
+
return false
|
|
5
|
+
end
|
|
6
|
+
if status == "error" then
|
|
7
|
+
return true
|
|
8
|
+
end
|
|
9
|
+
return delayElapsed
|
|
10
|
+
end
|
|
11
|
+
return {
|
|
12
|
+
resolveAvatarFallbackVisible = resolveAvatarFallbackVisible,
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type React from "@rbxts/react";
|
|
2
|
+
import type { AvatarStatus } from "./state";
|
|
3
|
+
export type AvatarContextValue = {
|
|
4
|
+
src?: string;
|
|
5
|
+
status: AvatarStatus;
|
|
6
|
+
setStatus: (status: AvatarStatus) => void;
|
|
7
|
+
delayElapsed: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type AvatarProps = {
|
|
10
|
+
src?: string;
|
|
11
|
+
delayMs?: number;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
export type AvatarImageProps = {
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
src?: string;
|
|
17
|
+
children?: React.ReactElement;
|
|
18
|
+
};
|
|
19
|
+
export type AvatarFallbackProps = {
|
|
20
|
+
asChild?: boolean;
|
|
21
|
+
children?: React.ReactElement;
|
|
22
|
+
};
|
package/out/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AvatarFallback } from "./Avatar/AvatarFallback";
|
|
2
|
+
import { AvatarImage } from "./Avatar/AvatarImage";
|
|
3
|
+
import { AvatarRoot } from "./Avatar/AvatarRoot";
|
|
4
|
+
export declare const Avatar: {
|
|
5
|
+
readonly Root: typeof AvatarRoot;
|
|
6
|
+
readonly Image: typeof AvatarImage;
|
|
7
|
+
readonly Fallback: typeof AvatarFallback;
|
|
8
|
+
};
|
|
9
|
+
export type { AvatarStatus } from "./Avatar/state";
|
|
10
|
+
export { resolveAvatarFallbackVisible } from "./Avatar/state";
|
|
11
|
+
export type { AvatarContextValue, AvatarFallbackProps, AvatarImageProps, AvatarProps } from "./Avatar/types";
|
package/out/init.luau
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local exports = {}
|
|
4
|
+
local AvatarFallback = TS.import(script, script, "Avatar", "AvatarFallback").AvatarFallback
|
|
5
|
+
local AvatarImage = TS.import(script, script, "Avatar", "AvatarImage").AvatarImage
|
|
6
|
+
local AvatarRoot = TS.import(script, script, "Avatar", "AvatarRoot").AvatarRoot
|
|
7
|
+
local Avatar = {
|
|
8
|
+
Root = AvatarRoot,
|
|
9
|
+
Image = AvatarImage,
|
|
10
|
+
Fallback = AvatarFallback,
|
|
11
|
+
}
|
|
12
|
+
exports.resolveAvatarFallbackVisible = TS.import(script, script, "Avatar", "state").resolveAvatarFallbackVisible
|
|
13
|
+
exports.Avatar = Avatar
|
|
14
|
+
return exports
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lattice-ui/avatar",
|
|
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,33 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useAvatarContext } from "./context";
|
|
3
|
+
import { resolveAvatarFallbackVisible } from "./state";
|
|
4
|
+
import type { AvatarFallbackProps } from "./types";
|
|
5
|
+
|
|
6
|
+
export function AvatarFallback(props: AvatarFallbackProps) {
|
|
7
|
+
const avatarContext = useAvatarContext();
|
|
8
|
+
const visible = resolveAvatarFallbackVisible(avatarContext.status, avatarContext.delayElapsed);
|
|
9
|
+
|
|
10
|
+
if (props.asChild) {
|
|
11
|
+
const child = props.children;
|
|
12
|
+
if (!child) {
|
|
13
|
+
error("[AvatarFallback] `asChild` requires a child element.");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return <Slot Visible={visible}>{child}</Slot>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<textlabel
|
|
21
|
+
BackgroundColor3={Color3.fromRGB(65, 72, 89)}
|
|
22
|
+
BorderSizePixel={0}
|
|
23
|
+
Size={UDim2.fromOffset(40, 40)}
|
|
24
|
+
Text="AB"
|
|
25
|
+
TextColor3={Color3.fromRGB(235, 240, 248)}
|
|
26
|
+
TextSize={14}
|
|
27
|
+
Visible={visible}
|
|
28
|
+
>
|
|
29
|
+
<uicorner CornerRadius={new UDim(1, 0)} />
|
|
30
|
+
{props.children}
|
|
31
|
+
</textlabel>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useAvatarContext } from "./context";
|
|
3
|
+
import type { AvatarImageProps } from "./types";
|
|
4
|
+
|
|
5
|
+
function toImageLabel(instance: Instance | undefined) {
|
|
6
|
+
if (!instance || !instance.IsA("ImageLabel")) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return instance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function AvatarImage(props: AvatarImageProps) {
|
|
14
|
+
const avatarContext = useAvatarContext();
|
|
15
|
+
const source = props.src ?? avatarContext.src;
|
|
16
|
+
|
|
17
|
+
const imageRef = React.useRef<ImageLabel>();
|
|
18
|
+
|
|
19
|
+
const setImageRef = React.useCallback((instance: Instance | undefined) => {
|
|
20
|
+
imageRef.current = toImageLabel(instance);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (source === undefined || source.size() === 0) {
|
|
25
|
+
avatarContext.setStatus("error");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
avatarContext.setStatus("loading");
|
|
30
|
+
}, [avatarContext, source]);
|
|
31
|
+
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
const image = imageRef.current;
|
|
34
|
+
if (!image) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (image.IsLoaded) {
|
|
39
|
+
avatarContext.setStatus("loaded");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const connection = image.GetPropertyChangedSignal("IsLoaded").Connect(() => {
|
|
43
|
+
if (image.IsLoaded) {
|
|
44
|
+
avatarContext.setStatus("loaded");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
connection.Disconnect();
|
|
50
|
+
};
|
|
51
|
+
}, [avatarContext, source]);
|
|
52
|
+
|
|
53
|
+
if (props.asChild) {
|
|
54
|
+
const child = props.children;
|
|
55
|
+
if (!child) {
|
|
56
|
+
error("[AvatarImage] `asChild` requires a child element.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Slot Image={source ?? ""} Visible={avatarContext.status === "loaded"} ref={setImageRef}>
|
|
61
|
+
{child}
|
|
62
|
+
</Slot>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<imagelabel
|
|
68
|
+
BackgroundTransparency={1}
|
|
69
|
+
BorderSizePixel={0}
|
|
70
|
+
Image={source ?? ""}
|
|
71
|
+
Size={UDim2.fromOffset(40, 40)}
|
|
72
|
+
Visible={avatarContext.status === "loaded"}
|
|
73
|
+
ref={setImageRef}
|
|
74
|
+
>
|
|
75
|
+
<uicorner CornerRadius={new UDim(1, 0)} />
|
|
76
|
+
</imagelabel>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { React } from "@lattice-ui/core";
|
|
2
|
+
import { AvatarContextProvider } from "./context";
|
|
3
|
+
import type { AvatarStatus } from "./state";
|
|
4
|
+
import type { AvatarProps } from "./types";
|
|
5
|
+
|
|
6
|
+
export function AvatarRoot(props: AvatarProps) {
|
|
7
|
+
const delayMs = math.max(0, props.delayMs ?? 250);
|
|
8
|
+
const hasSource = props.src !== undefined && props.src.size() > 0;
|
|
9
|
+
|
|
10
|
+
const [status, setStatus] = React.useState<AvatarStatus>(hasSource ? "loading" : "error");
|
|
11
|
+
const [delayElapsed, setDelayElapsed] = React.useState(!hasSource);
|
|
12
|
+
const sequenceRef = React.useRef(0);
|
|
13
|
+
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
sequenceRef.current += 1;
|
|
16
|
+
const sequence = sequenceRef.current;
|
|
17
|
+
|
|
18
|
+
if (props.src === undefined || props.src.size() === 0) {
|
|
19
|
+
setStatus("error");
|
|
20
|
+
setDelayElapsed(true);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setStatus("loading");
|
|
25
|
+
setDelayElapsed(false);
|
|
26
|
+
|
|
27
|
+
const delaySeconds = delayMs / 1000;
|
|
28
|
+
task.delay(delaySeconds, () => {
|
|
29
|
+
if (sequenceRef.current !== sequence) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setDelayElapsed(true);
|
|
34
|
+
});
|
|
35
|
+
}, [delayMs, props.src]);
|
|
36
|
+
|
|
37
|
+
const contextValue = React.useMemo(
|
|
38
|
+
() => ({
|
|
39
|
+
src: props.src,
|
|
40
|
+
status,
|
|
41
|
+
setStatus,
|
|
42
|
+
delayElapsed,
|
|
43
|
+
}),
|
|
44
|
+
[delayElapsed, props.src, status],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return <AvatarContextProvider value={contextValue}>{props.children}</AvatarContextProvider>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { AvatarRoot as Avatar };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type AvatarStatus = "idle" | "loading" | "loaded" | "error";
|
|
2
|
+
|
|
3
|
+
export function resolveAvatarFallbackVisible(status: AvatarStatus, delayElapsed: boolean) {
|
|
4
|
+
if (status === "loaded") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (status === "error") {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return delayElapsed;
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type React from "@rbxts/react";
|
|
2
|
+
import type { AvatarStatus } from "./state";
|
|
3
|
+
|
|
4
|
+
export type AvatarContextValue = {
|
|
5
|
+
src?: string;
|
|
6
|
+
status: AvatarStatus;
|
|
7
|
+
setStatus: (status: AvatarStatus) => void;
|
|
8
|
+
delayElapsed: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type AvatarProps = {
|
|
12
|
+
src?: string;
|
|
13
|
+
delayMs?: number;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AvatarImageProps = {
|
|
18
|
+
asChild?: boolean;
|
|
19
|
+
src?: string;
|
|
20
|
+
children?: React.ReactElement;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type AvatarFallbackProps = {
|
|
24
|
+
asChild?: boolean;
|
|
25
|
+
children?: React.ReactElement;
|
|
26
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AvatarFallback } from "./Avatar/AvatarFallback";
|
|
2
|
+
import { AvatarImage } from "./Avatar/AvatarImage";
|
|
3
|
+
import { AvatarRoot } from "./Avatar/AvatarRoot";
|
|
4
|
+
|
|
5
|
+
export const Avatar = {
|
|
6
|
+
Root: AvatarRoot,
|
|
7
|
+
Image: AvatarImage,
|
|
8
|
+
Fallback: AvatarFallback,
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type { AvatarStatus } from "./Avatar/state";
|
|
12
|
+
export { resolveAvatarFallbackVisible } from "./Avatar/state";
|
|
13
|
+
export type { AvatarContextValue, AvatarFallbackProps, AvatarImageProps, AvatarProps } from "./Avatar/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
|
+
}
|