@lattice-ui/text-field 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 +18 -0
- package/out/TextField/TextFieldDescription.d.ts +3 -0
- package/out/TextField/TextFieldDescription.luau +31 -0
- package/out/TextField/TextFieldInput.d.ts +3 -0
- package/out/TextField/TextFieldInput.luau +74 -0
- package/out/TextField/TextFieldLabel.d.ts +3 -0
- package/out/TextField/TextFieldLabel.luau +49 -0
- package/out/TextField/TextFieldMessage.d.ts +3 -0
- package/out/TextField/TextFieldMessage.luau +31 -0
- package/out/TextField/TextFieldRoot.d.ts +3 -0
- package/out/TextField/TextFieldRoot.luau +57 -0
- package/out/TextField/context.d.ts +3 -0
- package/out/TextField/context.luau +10 -0
- package/out/TextField/types.d.ts +44 -0
- package/out/TextField/types.luau +2 -0
- package/out/index.d.ts +13 -0
- package/out/init.luau +17 -0
- package/package.json +23 -0
- package/src/TextField/TextFieldDescription.tsx +32 -0
- package/src/TextField/TextFieldInput.tsx +89 -0
- package/src/TextField/TextFieldLabel.tsx +47 -0
- package/src/TextField/TextFieldMessage.tsx +38 -0
- package/src/TextField/TextFieldRoot.tsx +53 -0
- package/src/TextField/context.ts +6 -0
- package/src/TextField/types.ts +51 -0
- package/src/index.ts +24 -0
- package/tsconfig.json +16 -0
- package/tsconfig.typecheck.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @lattice-ui/text-field
|
|
2
|
+
|
|
3
|
+
Headless text input primitives for Roblox UI with controlled/uncontrolled state and commit events.
|
|
4
|
+
|
|
5
|
+
## Exports
|
|
6
|
+
|
|
7
|
+
- `TextField`
|
|
8
|
+
- `TextField.Root`
|
|
9
|
+
- `TextField.Input`
|
|
10
|
+
- `TextField.Label`
|
|
11
|
+
- `TextField.Description`
|
|
12
|
+
- `TextField.Message`
|
|
13
|
+
|
|
14
|
+
## Notes
|
|
15
|
+
|
|
16
|
+
- `onValueChange` runs when input text changes.
|
|
17
|
+
- `onValueCommit` runs on `FocusLost`/enter-style commit moments.
|
|
18
|
+
- `disabled` and `readOnly` are enforced at the root context and reflected by `TextFieldInput`.
|
|
@@ -0,0 +1,31 @@
|
|
|
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 useTextFieldContext = TS.import(script, script.Parent, "context").useTextFieldContext
|
|
7
|
+
local function TextFieldDescription(props)
|
|
8
|
+
local textFieldContext = useTextFieldContext()
|
|
9
|
+
if props.asChild then
|
|
10
|
+
local child = props.children
|
|
11
|
+
if not child then
|
|
12
|
+
error("[TextFieldDescription] `asChild` requires a child element.")
|
|
13
|
+
end
|
|
14
|
+
return React.createElement(Slot, {
|
|
15
|
+
Name = "TextFieldDescription",
|
|
16
|
+
Text = "Description",
|
|
17
|
+
}, child)
|
|
18
|
+
end
|
|
19
|
+
return React.createElement("textlabel", {
|
|
20
|
+
BackgroundTransparency = 1,
|
|
21
|
+
BorderSizePixel = 0,
|
|
22
|
+
Size = UDim2.fromOffset(300, 20),
|
|
23
|
+
Text = "Description",
|
|
24
|
+
TextColor3 = if textFieldContext.disabled then Color3.fromRGB(132, 139, 154) else Color3.fromRGB(170, 179, 195),
|
|
25
|
+
TextSize = 13,
|
|
26
|
+
TextXAlignment = Enum.TextXAlignment.Left,
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
return {
|
|
30
|
+
TextFieldDescription = TextFieldDescription,
|
|
31
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 useTextFieldContext = TS.import(script, script.Parent, "context").useTextFieldContext
|
|
7
|
+
local function toTextBox(instance)
|
|
8
|
+
if not instance or not instance:IsA("TextBox") then
|
|
9
|
+
return nil
|
|
10
|
+
end
|
|
11
|
+
return instance
|
|
12
|
+
end
|
|
13
|
+
local function TextFieldInput(props)
|
|
14
|
+
local textFieldContext = useTextFieldContext()
|
|
15
|
+
local disabled = textFieldContext.disabled or props.disabled == true
|
|
16
|
+
local readOnly = textFieldContext.readOnly or props.readOnly == true
|
|
17
|
+
local setInputRef = React.useCallback(function(instance)
|
|
18
|
+
textFieldContext.inputRef.current = toTextBox(instance)
|
|
19
|
+
end, { textFieldContext.inputRef })
|
|
20
|
+
local handleTextChanged = React.useCallback(function(textBox)
|
|
21
|
+
if disabled or readOnly then
|
|
22
|
+
if textBox.Text ~= textFieldContext.value then
|
|
23
|
+
textBox.Text = textFieldContext.value
|
|
24
|
+
end
|
|
25
|
+
return nil
|
|
26
|
+
end
|
|
27
|
+
textFieldContext.setValue(textBox.Text)
|
|
28
|
+
end, { disabled, readOnly, textFieldContext })
|
|
29
|
+
local handleFocusLost = React.useCallback(function(textBox)
|
|
30
|
+
if disabled then
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
33
|
+
textFieldContext.commitValue(textBox.Text)
|
|
34
|
+
end, { disabled, textFieldContext })
|
|
35
|
+
local sharedProps = {
|
|
36
|
+
Active = not disabled,
|
|
37
|
+
ClearTextOnFocus = false,
|
|
38
|
+
Selectable = not disabled,
|
|
39
|
+
Text = textFieldContext.value,
|
|
40
|
+
TextEditable = not disabled and not readOnly,
|
|
41
|
+
Change = {
|
|
42
|
+
Text = handleTextChanged,
|
|
43
|
+
},
|
|
44
|
+
Event = {
|
|
45
|
+
FocusLost = handleFocusLost,
|
|
46
|
+
},
|
|
47
|
+
ref = setInputRef,
|
|
48
|
+
}
|
|
49
|
+
if props.asChild then
|
|
50
|
+
local child = props.children
|
|
51
|
+
if not child then
|
|
52
|
+
error("[TextFieldInput] `asChild` requires a child element.")
|
|
53
|
+
end
|
|
54
|
+
local _attributes = table.clone(sharedProps)
|
|
55
|
+
setmetatable(_attributes, nil)
|
|
56
|
+
return React.createElement(Slot, _attributes, child)
|
|
57
|
+
end
|
|
58
|
+
local _attributes = table.clone(sharedProps)
|
|
59
|
+
setmetatable(_attributes, nil)
|
|
60
|
+
_attributes.BackgroundColor3 = Color3.fromRGB(39, 46, 61)
|
|
61
|
+
_attributes.BorderSizePixel = 0
|
|
62
|
+
_attributes.PlaceholderText = "Type..."
|
|
63
|
+
_attributes.Size = UDim2.fromOffset(240, 36)
|
|
64
|
+
_attributes.TextColor3 = if disabled then Color3.fromRGB(137, 145, 162) else Color3.fromRGB(235, 240, 248)
|
|
65
|
+
_attributes.TextSize = 15
|
|
66
|
+
_attributes.TextXAlignment = Enum.TextXAlignment.Left
|
|
67
|
+
return React.createElement("textbox", _attributes, React.createElement("uipadding", {
|
|
68
|
+
PaddingLeft = UDim.new(0, 10),
|
|
69
|
+
PaddingRight = UDim.new(0, 10),
|
|
70
|
+
}))
|
|
71
|
+
end
|
|
72
|
+
return {
|
|
73
|
+
TextFieldInput = TextFieldInput,
|
|
74
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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 useTextFieldContext = TS.import(script, script.Parent, "context").useTextFieldContext
|
|
7
|
+
local function TextFieldLabel(props)
|
|
8
|
+
local textFieldContext = useTextFieldContext()
|
|
9
|
+
local disabled = textFieldContext.disabled
|
|
10
|
+
local handleActivated = React.useCallback(function()
|
|
11
|
+
if disabled then
|
|
12
|
+
return nil
|
|
13
|
+
end
|
|
14
|
+
local _result = textFieldContext.inputRef.current
|
|
15
|
+
if _result ~= nil then
|
|
16
|
+
_result:CaptureFocus()
|
|
17
|
+
end
|
|
18
|
+
end, { disabled, textFieldContext.inputRef })
|
|
19
|
+
local sharedProps = {
|
|
20
|
+
Active = not disabled,
|
|
21
|
+
Selectable = not disabled,
|
|
22
|
+
Text = "Label",
|
|
23
|
+
Event = {
|
|
24
|
+
Activated = handleActivated,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
if props.asChild then
|
|
28
|
+
local child = props.children
|
|
29
|
+
if not child then
|
|
30
|
+
error("[TextFieldLabel] `asChild` requires a child element.")
|
|
31
|
+
end
|
|
32
|
+
local _attributes = table.clone(sharedProps)
|
|
33
|
+
setmetatable(_attributes, nil)
|
|
34
|
+
return React.createElement(Slot, _attributes, child)
|
|
35
|
+
end
|
|
36
|
+
local _attributes = table.clone(sharedProps)
|
|
37
|
+
setmetatable(_attributes, nil)
|
|
38
|
+
_attributes.AutoButtonColor = false
|
|
39
|
+
_attributes.BackgroundTransparency = 1
|
|
40
|
+
_attributes.BorderSizePixel = 0
|
|
41
|
+
_attributes.Size = UDim2.fromOffset(240, 22)
|
|
42
|
+
_attributes.TextColor3 = if disabled then Color3.fromRGB(149, 157, 173) else Color3.fromRGB(225, 231, 241)
|
|
43
|
+
_attributes.TextSize = 14
|
|
44
|
+
_attributes.TextXAlignment = Enum.TextXAlignment.Left
|
|
45
|
+
return React.createElement("textbutton", _attributes)
|
|
46
|
+
end
|
|
47
|
+
return {
|
|
48
|
+
TextFieldLabel = TextFieldLabel,
|
|
49
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 useTextFieldContext = TS.import(script, script.Parent, "context").useTextFieldContext
|
|
7
|
+
local function TextFieldMessage(props)
|
|
8
|
+
local textFieldContext = useTextFieldContext()
|
|
9
|
+
if props.asChild then
|
|
10
|
+
local child = props.children
|
|
11
|
+
if not child then
|
|
12
|
+
error("[TextFieldMessage] `asChild` requires a child element.")
|
|
13
|
+
end
|
|
14
|
+
return React.createElement(Slot, {
|
|
15
|
+
Name = "TextFieldMessage",
|
|
16
|
+
Text = "Message",
|
|
17
|
+
}, child)
|
|
18
|
+
end
|
|
19
|
+
return React.createElement("textlabel", {
|
|
20
|
+
BackgroundTransparency = 1,
|
|
21
|
+
BorderSizePixel = 0,
|
|
22
|
+
Size = UDim2.fromOffset(300, 20),
|
|
23
|
+
Text = "Message",
|
|
24
|
+
TextColor3 = if textFieldContext.invalid == true then Color3.fromRGB(255, 128, 128) elseif textFieldContext.disabled then Color3.fromRGB(132, 139, 154) else Color3.fromRGB(170, 179, 195),
|
|
25
|
+
TextSize = 13,
|
|
26
|
+
TextXAlignment = Enum.TextXAlignment.Left,
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
return {
|
|
30
|
+
TextFieldMessage = TextFieldMessage,
|
|
31
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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 TextFieldContextProvider = TS.import(script, script.Parent, "context").TextFieldContextProvider
|
|
7
|
+
local function TextFieldRoot(props)
|
|
8
|
+
local _object = {
|
|
9
|
+
value = props.value,
|
|
10
|
+
}
|
|
11
|
+
local _left = "defaultValue"
|
|
12
|
+
local _condition = props.defaultValue
|
|
13
|
+
if _condition == nil then
|
|
14
|
+
_condition = ""
|
|
15
|
+
end
|
|
16
|
+
_object[_left] = _condition
|
|
17
|
+
_object.onChange = props.onValueChange
|
|
18
|
+
local _binding = useControllableState(_object)
|
|
19
|
+
local value = _binding[1]
|
|
20
|
+
local setValueState = _binding[2]
|
|
21
|
+
local disabled = props.disabled == true
|
|
22
|
+
local readOnly = props.readOnly == true
|
|
23
|
+
local required = props.required == true
|
|
24
|
+
local invalid = props.invalid == true
|
|
25
|
+
local inputRef = React.useRef()
|
|
26
|
+
local setValue = React.useCallback(function(nextValue)
|
|
27
|
+
if disabled or readOnly then
|
|
28
|
+
return nil
|
|
29
|
+
end
|
|
30
|
+
setValueState(nextValue)
|
|
31
|
+
end, { disabled, readOnly, setValueState })
|
|
32
|
+
local commitValue = React.useCallback(function(nextValue)
|
|
33
|
+
local _result = props.onValueCommit
|
|
34
|
+
if _result ~= nil then
|
|
35
|
+
_result(nextValue)
|
|
36
|
+
end
|
|
37
|
+
end, { props.onValueCommit })
|
|
38
|
+
local contextValue = React.useMemo(function()
|
|
39
|
+
return {
|
|
40
|
+
value = value,
|
|
41
|
+
setValue = setValue,
|
|
42
|
+
commitValue = commitValue,
|
|
43
|
+
disabled = disabled,
|
|
44
|
+
readOnly = readOnly,
|
|
45
|
+
required = required,
|
|
46
|
+
invalid = invalid,
|
|
47
|
+
name = props.name,
|
|
48
|
+
inputRef = inputRef,
|
|
49
|
+
}
|
|
50
|
+
end, { commitValue, disabled, invalid, props.name, readOnly, required, setValue, value })
|
|
51
|
+
return React.createElement(TextFieldContextProvider, {
|
|
52
|
+
value = contextValue,
|
|
53
|
+
}, props.children)
|
|
54
|
+
end
|
|
55
|
+
return {
|
|
56
|
+
TextFieldRoot = TextFieldRoot,
|
|
57
|
+
}
|
|
@@ -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("TextField")
|
|
5
|
+
local TextFieldContextProvider = _binding[1]
|
|
6
|
+
local useTextFieldContext = _binding[2]
|
|
7
|
+
return {
|
|
8
|
+
TextFieldContextProvider = TextFieldContextProvider,
|
|
9
|
+
useTextFieldContext = useTextFieldContext,
|
|
10
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type React from "@rbxts/react";
|
|
2
|
+
export type TextFieldSetValue = (value: string) => void;
|
|
3
|
+
export type TextFieldCommitValue = (value: string) => void;
|
|
4
|
+
export type TextFieldContextValue = {
|
|
5
|
+
value: string;
|
|
6
|
+
setValue: TextFieldSetValue;
|
|
7
|
+
commitValue: TextFieldCommitValue;
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
readOnly: boolean;
|
|
10
|
+
required: boolean;
|
|
11
|
+
invalid: boolean;
|
|
12
|
+
name?: string;
|
|
13
|
+
inputRef: React.MutableRefObject<TextBox | undefined>;
|
|
14
|
+
};
|
|
15
|
+
export type TextFieldProps = {
|
|
16
|
+
value?: string;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
onValueChange?: (value: string) => void;
|
|
19
|
+
onValueCommit?: (value: string) => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
readOnly?: boolean;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
invalid?: boolean;
|
|
24
|
+
name?: string;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
};
|
|
27
|
+
export type TextFieldInputProps = {
|
|
28
|
+
asChild?: boolean;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
readOnly?: boolean;
|
|
31
|
+
children?: React.ReactElement;
|
|
32
|
+
};
|
|
33
|
+
export type TextFieldLabelProps = {
|
|
34
|
+
asChild?: boolean;
|
|
35
|
+
children?: React.ReactElement;
|
|
36
|
+
};
|
|
37
|
+
export type TextFieldDescriptionProps = {
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
children?: React.ReactElement;
|
|
40
|
+
};
|
|
41
|
+
export type TextFieldMessageProps = {
|
|
42
|
+
asChild?: boolean;
|
|
43
|
+
children?: React.ReactElement;
|
|
44
|
+
};
|
package/out/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TextFieldDescription } from "./TextField/TextFieldDescription";
|
|
2
|
+
import { TextFieldInput } from "./TextField/TextFieldInput";
|
|
3
|
+
import { TextFieldLabel } from "./TextField/TextFieldLabel";
|
|
4
|
+
import { TextFieldMessage } from "./TextField/TextFieldMessage";
|
|
5
|
+
import { TextFieldRoot } from "./TextField/TextFieldRoot";
|
|
6
|
+
export declare const TextField: {
|
|
7
|
+
readonly Root: typeof TextFieldRoot;
|
|
8
|
+
readonly Input: typeof TextFieldInput;
|
|
9
|
+
readonly Label: typeof TextFieldLabel;
|
|
10
|
+
readonly Description: typeof TextFieldDescription;
|
|
11
|
+
readonly Message: typeof TextFieldMessage;
|
|
12
|
+
};
|
|
13
|
+
export type { TextFieldCommitValue, TextFieldContextValue, TextFieldDescriptionProps, TextFieldInputProps, TextFieldLabelProps, TextFieldMessageProps, TextFieldProps, TextFieldSetValue, } from "./TextField/types";
|
package/out/init.luau
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local TextFieldDescription = TS.import(script, script, "TextField", "TextFieldDescription").TextFieldDescription
|
|
4
|
+
local TextFieldInput = TS.import(script, script, "TextField", "TextFieldInput").TextFieldInput
|
|
5
|
+
local TextFieldLabel = TS.import(script, script, "TextField", "TextFieldLabel").TextFieldLabel
|
|
6
|
+
local TextFieldMessage = TS.import(script, script, "TextField", "TextFieldMessage").TextFieldMessage
|
|
7
|
+
local TextFieldRoot = TS.import(script, script, "TextField", "TextFieldRoot").TextFieldRoot
|
|
8
|
+
local TextField = {
|
|
9
|
+
Root = TextFieldRoot,
|
|
10
|
+
Input = TextFieldInput,
|
|
11
|
+
Label = TextFieldLabel,
|
|
12
|
+
Description = TextFieldDescription,
|
|
13
|
+
Message = TextFieldMessage,
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
TextField = TextField,
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lattice-ui/text-field",
|
|
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,32 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useTextFieldContext } from "./context";
|
|
3
|
+
import type { TextFieldDescriptionProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function TextFieldDescription(props: TextFieldDescriptionProps) {
|
|
6
|
+
const textFieldContext = useTextFieldContext();
|
|
7
|
+
|
|
8
|
+
if (props.asChild) {
|
|
9
|
+
const child = props.children;
|
|
10
|
+
if (!child) {
|
|
11
|
+
error("[TextFieldDescription] `asChild` requires a child element.");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Slot Name="TextFieldDescription" Text="Description">
|
|
16
|
+
{child}
|
|
17
|
+
</Slot>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<textlabel
|
|
23
|
+
BackgroundTransparency={1}
|
|
24
|
+
BorderSizePixel={0}
|
|
25
|
+
Size={UDim2.fromOffset(300, 20)}
|
|
26
|
+
Text="Description"
|
|
27
|
+
TextColor3={textFieldContext.disabled ? Color3.fromRGB(132, 139, 154) : Color3.fromRGB(170, 179, 195)}
|
|
28
|
+
TextSize={13}
|
|
29
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useTextFieldContext } from "./context";
|
|
3
|
+
import type { TextFieldInputProps } from "./types";
|
|
4
|
+
|
|
5
|
+
function toTextBox(instance: Instance | undefined) {
|
|
6
|
+
if (!instance || !instance.IsA("TextBox")) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return instance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function TextFieldInput(props: TextFieldInputProps) {
|
|
14
|
+
const textFieldContext = useTextFieldContext();
|
|
15
|
+
const disabled = textFieldContext.disabled || props.disabled === true;
|
|
16
|
+
const readOnly = textFieldContext.readOnly || props.readOnly === true;
|
|
17
|
+
|
|
18
|
+
const setInputRef = React.useCallback(
|
|
19
|
+
(instance: Instance | undefined) => {
|
|
20
|
+
textFieldContext.inputRef.current = toTextBox(instance);
|
|
21
|
+
},
|
|
22
|
+
[textFieldContext.inputRef],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const handleTextChanged = React.useCallback(
|
|
26
|
+
(textBox: TextBox) => {
|
|
27
|
+
if (disabled || readOnly) {
|
|
28
|
+
if (textBox.Text !== textFieldContext.value) {
|
|
29
|
+
textBox.Text = textFieldContext.value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
textFieldContext.setValue(textBox.Text);
|
|
36
|
+
},
|
|
37
|
+
[disabled, readOnly, textFieldContext],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const handleFocusLost = React.useCallback(
|
|
41
|
+
(textBox: TextBox) => {
|
|
42
|
+
if (disabled) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
textFieldContext.commitValue(textBox.Text);
|
|
47
|
+
},
|
|
48
|
+
[disabled, textFieldContext],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const sharedProps = {
|
|
52
|
+
Active: !disabled,
|
|
53
|
+
ClearTextOnFocus: false,
|
|
54
|
+
Selectable: !disabled,
|
|
55
|
+
Text: textFieldContext.value,
|
|
56
|
+
TextEditable: !disabled && !readOnly,
|
|
57
|
+
Change: {
|
|
58
|
+
Text: handleTextChanged,
|
|
59
|
+
},
|
|
60
|
+
Event: {
|
|
61
|
+
FocusLost: handleFocusLost,
|
|
62
|
+
},
|
|
63
|
+
ref: setInputRef,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (props.asChild) {
|
|
67
|
+
const child = props.children;
|
|
68
|
+
if (!child) {
|
|
69
|
+
error("[TextFieldInput] `asChild` requires a child element.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return <Slot {...sharedProps}>{child}</Slot>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<textbox
|
|
77
|
+
{...sharedProps}
|
|
78
|
+
BackgroundColor3={Color3.fromRGB(39, 46, 61)}
|
|
79
|
+
BorderSizePixel={0}
|
|
80
|
+
PlaceholderText="Type..."
|
|
81
|
+
Size={UDim2.fromOffset(240, 36)}
|
|
82
|
+
TextColor3={disabled ? Color3.fromRGB(137, 145, 162) : Color3.fromRGB(235, 240, 248)}
|
|
83
|
+
TextSize={15}
|
|
84
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
85
|
+
>
|
|
86
|
+
<uipadding PaddingLeft={new UDim(0, 10)} PaddingRight={new UDim(0, 10)} />
|
|
87
|
+
</textbox>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useTextFieldContext } from "./context";
|
|
3
|
+
import type { TextFieldLabelProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function TextFieldLabel(props: TextFieldLabelProps) {
|
|
6
|
+
const textFieldContext = useTextFieldContext();
|
|
7
|
+
const disabled = textFieldContext.disabled;
|
|
8
|
+
|
|
9
|
+
const handleActivated = React.useCallback(() => {
|
|
10
|
+
if (disabled) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
textFieldContext.inputRef.current?.CaptureFocus();
|
|
15
|
+
}, [disabled, textFieldContext.inputRef]);
|
|
16
|
+
|
|
17
|
+
const sharedProps = {
|
|
18
|
+
Active: !disabled,
|
|
19
|
+
Selectable: !disabled,
|
|
20
|
+
Text: "Label",
|
|
21
|
+
Event: {
|
|
22
|
+
Activated: handleActivated,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (props.asChild) {
|
|
27
|
+
const child = props.children;
|
|
28
|
+
if (!child) {
|
|
29
|
+
error("[TextFieldLabel] `asChild` requires a child element.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return <Slot {...sharedProps}>{child}</Slot>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<textbutton
|
|
37
|
+
{...sharedProps}
|
|
38
|
+
AutoButtonColor={false}
|
|
39
|
+
BackgroundTransparency={1}
|
|
40
|
+
BorderSizePixel={0}
|
|
41
|
+
Size={UDim2.fromOffset(240, 22)}
|
|
42
|
+
TextColor3={disabled ? Color3.fromRGB(149, 157, 173) : Color3.fromRGB(225, 231, 241)}
|
|
43
|
+
TextSize={14}
|
|
44
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { React, Slot } from "@lattice-ui/core";
|
|
2
|
+
import { useTextFieldContext } from "./context";
|
|
3
|
+
import type { TextFieldMessageProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function TextFieldMessage(props: TextFieldMessageProps) {
|
|
6
|
+
const textFieldContext = useTextFieldContext();
|
|
7
|
+
|
|
8
|
+
if (props.asChild) {
|
|
9
|
+
const child = props.children;
|
|
10
|
+
if (!child) {
|
|
11
|
+
error("[TextFieldMessage] `asChild` requires a child element.");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Slot Name="TextFieldMessage" Text="Message">
|
|
16
|
+
{child}
|
|
17
|
+
</Slot>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<textlabel
|
|
23
|
+
BackgroundTransparency={1}
|
|
24
|
+
BorderSizePixel={0}
|
|
25
|
+
Size={UDim2.fromOffset(300, 20)}
|
|
26
|
+
Text="Message"
|
|
27
|
+
TextColor3={
|
|
28
|
+
textFieldContext.invalid === true
|
|
29
|
+
? Color3.fromRGB(255, 128, 128)
|
|
30
|
+
: textFieldContext.disabled
|
|
31
|
+
? Color3.fromRGB(132, 139, 154)
|
|
32
|
+
: Color3.fromRGB(170, 179, 195)
|
|
33
|
+
}
|
|
34
|
+
TextSize={13}
|
|
35
|
+
TextXAlignment={Enum.TextXAlignment.Left}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { React, useControllableState } from "@lattice-ui/core";
|
|
2
|
+
import { TextFieldContextProvider } from "./context";
|
|
3
|
+
import type { TextFieldProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export function TextFieldRoot(props: TextFieldProps) {
|
|
6
|
+
const [value, setValueState] = useControllableState<string>({
|
|
7
|
+
value: props.value,
|
|
8
|
+
defaultValue: props.defaultValue ?? "",
|
|
9
|
+
onChange: props.onValueChange,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const disabled = props.disabled === true;
|
|
13
|
+
const readOnly = props.readOnly === true;
|
|
14
|
+
const required = props.required === true;
|
|
15
|
+
const invalid = props.invalid === true;
|
|
16
|
+
|
|
17
|
+
const inputRef = React.useRef<TextBox>();
|
|
18
|
+
|
|
19
|
+
const setValue = React.useCallback(
|
|
20
|
+
(nextValue: string) => {
|
|
21
|
+
if (disabled || readOnly) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setValueState(nextValue);
|
|
26
|
+
},
|
|
27
|
+
[disabled, readOnly, setValueState],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const commitValue = React.useCallback(
|
|
31
|
+
(nextValue: string) => {
|
|
32
|
+
props.onValueCommit?.(nextValue);
|
|
33
|
+
},
|
|
34
|
+
[props.onValueCommit],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const contextValue = React.useMemo(
|
|
38
|
+
() => ({
|
|
39
|
+
value,
|
|
40
|
+
setValue,
|
|
41
|
+
commitValue,
|
|
42
|
+
disabled,
|
|
43
|
+
readOnly,
|
|
44
|
+
required,
|
|
45
|
+
invalid,
|
|
46
|
+
name: props.name,
|
|
47
|
+
inputRef,
|
|
48
|
+
}),
|
|
49
|
+
[commitValue, disabled, invalid, props.name, readOnly, required, setValue, value],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return <TextFieldContextProvider value={contextValue}>{props.children}</TextFieldContextProvider>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createStrictContext } from "@lattice-ui/core";
|
|
2
|
+
import type { TextFieldContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
const [TextFieldContextProvider, useTextFieldContext] = createStrictContext<TextFieldContextValue>("TextField");
|
|
5
|
+
|
|
6
|
+
export { TextFieldContextProvider, useTextFieldContext };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type React from "@rbxts/react";
|
|
2
|
+
|
|
3
|
+
export type TextFieldSetValue = (value: string) => void;
|
|
4
|
+
export type TextFieldCommitValue = (value: string) => void;
|
|
5
|
+
|
|
6
|
+
export type TextFieldContextValue = {
|
|
7
|
+
value: string;
|
|
8
|
+
setValue: TextFieldSetValue;
|
|
9
|
+
commitValue: TextFieldCommitValue;
|
|
10
|
+
disabled: boolean;
|
|
11
|
+
readOnly: boolean;
|
|
12
|
+
required: boolean;
|
|
13
|
+
invalid: boolean;
|
|
14
|
+
name?: string;
|
|
15
|
+
inputRef: React.MutableRefObject<TextBox | undefined>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TextFieldProps = {
|
|
19
|
+
value?: string;
|
|
20
|
+
defaultValue?: string;
|
|
21
|
+
onValueChange?: (value: string) => void;
|
|
22
|
+
onValueCommit?: (value: string) => void;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
required?: boolean;
|
|
26
|
+
invalid?: boolean;
|
|
27
|
+
name?: string;
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type TextFieldInputProps = {
|
|
32
|
+
asChild?: boolean;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
readOnly?: boolean;
|
|
35
|
+
children?: React.ReactElement;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type TextFieldLabelProps = {
|
|
39
|
+
asChild?: boolean;
|
|
40
|
+
children?: React.ReactElement;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type TextFieldDescriptionProps = {
|
|
44
|
+
asChild?: boolean;
|
|
45
|
+
children?: React.ReactElement;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type TextFieldMessageProps = {
|
|
49
|
+
asChild?: boolean;
|
|
50
|
+
children?: React.ReactElement;
|
|
51
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TextFieldDescription } from "./TextField/TextFieldDescription";
|
|
2
|
+
import { TextFieldInput } from "./TextField/TextFieldInput";
|
|
3
|
+
import { TextFieldLabel } from "./TextField/TextFieldLabel";
|
|
4
|
+
import { TextFieldMessage } from "./TextField/TextFieldMessage";
|
|
5
|
+
import { TextFieldRoot } from "./TextField/TextFieldRoot";
|
|
6
|
+
|
|
7
|
+
export const TextField = {
|
|
8
|
+
Root: TextFieldRoot,
|
|
9
|
+
Input: TextFieldInput,
|
|
10
|
+
Label: TextFieldLabel,
|
|
11
|
+
Description: TextFieldDescription,
|
|
12
|
+
Message: TextFieldMessage,
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
TextFieldCommitValue,
|
|
17
|
+
TextFieldContextValue,
|
|
18
|
+
TextFieldDescriptionProps,
|
|
19
|
+
TextFieldInputProps,
|
|
20
|
+
TextFieldLabelProps,
|
|
21
|
+
TextFieldMessageProps,
|
|
22
|
+
TextFieldProps,
|
|
23
|
+
TextFieldSetValue,
|
|
24
|
+
} from "./TextField/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
|
+
}
|