@react-ui-org/react-ui 0.63.1 → 0.64.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.
Files changed (49) hide show
  1. package/.claude/agents/code-reviewer.md +122 -0
  2. package/.claude/rules/code.md +22 -0
  3. package/.claude/rules/docs.md +35 -0
  4. package/.claude/rules/frontend.md +72 -0
  5. package/.claude/rules/git.md +13 -0
  6. package/.claude/rules/safety-guards.md +9 -0
  7. package/.claude/rules/styling.md +32 -0
  8. package/.claude/rules/testing.md +59 -0
  9. package/.claude/settings.json +32 -0
  10. package/.claude/skills/commit/SKILL.md +54 -0
  11. package/.devcontainer/devcontainer.json +23 -0
  12. package/.mcp.json +8 -0
  13. package/CLAUDE.md +82 -0
  14. package/dist/react-ui.css +4 -4
  15. package/dist/react-ui.development.css +600 -590
  16. package/dist/react-ui.development.js +20 -10
  17. package/dist/react-ui.js +1 -1
  18. package/docker-compose.base.yml +93 -0
  19. package/docker-compose.yml.dist +41 -0
  20. package/opencode.json +10 -0
  21. package/package.json +3 -2
  22. package/scripts/auto-start-mkdocs.sh +5 -0
  23. package/scripts/auto-start-node.sh +33 -0
  24. package/scripts/mcps/chrome-host/README.md +118 -0
  25. package/scripts/mcps/chrome-host/cdp_proxy.py +90 -0
  26. package/scripts/mcps/chrome-host/mcp-entry.sh +33 -0
  27. package/scripts/mcps/chrome-host/mcp-launch.sh +36 -0
  28. package/scripts/mcps/chrome-host/mcp-setup.sh +70 -0
  29. package/scripts/mcps/chrome-host/start-host-chrome.sh +267 -0
  30. package/scripts/write-lockfile-hash.sh +15 -0
  31. package/setup.sh +107 -0
  32. package/src/components/Button/Button.jsx +4 -0
  33. package/src/components/Button/Button.module.scss +11 -0
  34. package/src/components/FileInputField/FileInputField.jsx +9 -2
  35. package/src/components/FormLayout/FormLayoutCustomField.jsx +4 -1
  36. package/src/components/FormLayout/FormLayoutCustomFieldContext.js +3 -0
  37. package/src/components/FormLayout/README.md +168 -161
  38. package/src/components/FormLayout/index.js +1 -0
  39. package/src/components/Popover/Popover.jsx +1 -0
  40. package/src/components/Popover/Popover.module.scss +1 -0
  41. package/src/components/Popover/README.md +29 -0
  42. package/src/components/Popover/_helpers/cleanPlacementStyle.js +1 -0
  43. package/src/components/Popover/_theme.scss +1 -0
  44. package/src/components/SelectField/SelectField.jsx +8 -3
  45. package/src/components/TextArea/TextArea.jsx +9 -2
  46. package/src/components/TextField/TextField.jsx +8 -3
  47. package/src/theme.scss +1 -0
  48. package/.env.playwright +0 -9
  49. package/.env.playwright.dist +0 -9
@@ -0,0 +1,267 @@
1
+ #!/bin/sh
2
+
3
+ # Run on the HOST (macOS or Linux). Launches a dedicated, isolated Chrome with
4
+ # the DevTools (CDP) endpoint enabled so a containerized assistant can drive it.
5
+ #
6
+ # It uses its own dedicated --user-data-dir, so your everyday Chrome and logins
7
+ # are untouched. A separate profile is also required: Chrome only opens a debug
8
+ # port for a fresh instance, not for an already-running default profile.
9
+ #
10
+ # That profile is persistent (reused across runs, kept under $HOME) -- not
11
+ # ephemeral or sandboxed. Anything you sign in to here stays on disk and is
12
+ # reachable over the unauthenticated CDP port, so avoid logging in to sensitive
13
+ # accounts in this browser.
14
+ #
15
+ # Overrides (shell wins over the repo's .env); see README -> Platform support
16
+ # for the bind/platform details:
17
+ # CHROME_DEBUG_PORT CDP port (default 9333)
18
+ # CHROME_BIN explicit browser binary (else auto-detected)
19
+ # CHROME_DEBUG_BIND bind address (else auto-detected: loopback on Docker
20
+ # Desktop, docker bridge gateway on native Engine).
21
+ # SECURITY: a non-loopback bind exposes the port -- firewall it.
22
+ #
23
+ # Profile is always ~/.chrome-debug-profile-<COMPOSE_PROJECT_NAME> (not configurable).
24
+
25
+ set -eu
26
+
27
+ # Opens a real browser window, so it must run on the HOST, not in a container
28
+ # (/.dockerenv exists only inside one).
29
+ if [ -f /.dockerenv ]; then
30
+ echo "chrome-host: error: run start-host-chrome.sh on your HOST, not in a container." >&2
31
+ echo "chrome-host: it launches a Chrome window on your desktop for the containerized" >&2
32
+ echo "chrome-host: assistant to drive. Open a host terminal and run it there." >&2
33
+ exit 1
34
+ fi
35
+
36
+ # Fall back to the repo's .env (the file Docker Compose reads) so this launcher
37
+ # and the in-container proxy share one source of truth. Precedence: shell > .env
38
+ # > the defaults below.
39
+ env_root="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
40
+ while [ "$env_root" != "/" ] && [ ! -f "$env_root/docker-compose.yml" ]; do
41
+ env_root="$(dirname "$env_root")"
42
+ done
43
+ ENV_FILE="$env_root/.env"
44
+ # For each host knob, keep a value already set in the shell; otherwise eval the
45
+ # matching .env line. Letting the shell run the assignment strips quotes and
46
+ # inline comments for free. Values containing spaces must be quoted in .env.
47
+ if [ -f "$ENV_FILE" ]; then
48
+ for env_key in CHROME_DEBUG_PORT CHROME_BIN CHROME_DEBUG_BIND COMPOSE_PROJECT_NAME COMPOSE_DOCS_SERVER_PORT; do
49
+ eval "[ -n \"\${$env_key+x}\" ]" && continue
50
+ eval "$(grep -E "^[[:space:]]*$env_key=" "$ENV_FILE" | tail -n 1 || true)"
51
+ done
52
+ fi
53
+
54
+ PORT="${CHROME_DEBUG_PORT:-9333}"
55
+
56
+ # Preflight: curl drives every CDP readiness probe below, on every platform.
57
+ if ! command -v curl >/dev/null 2>&1; then
58
+ echo "chrome-host: error: 'curl' is required to probe Chrome's CDP endpoint but is not installed." >&2
59
+ exit 1
60
+ fi
61
+
62
+ # Bind address. Explicit CHROME_DEBUG_BIND wins; otherwise auto-detect: loopback
63
+ # on Docker Desktop (incl. macOS), else the docker bridge gateway for native
64
+ # Engine. Binding to the gateway (not 0.0.0.0) keeps the port off your wider
65
+ # network. See README -> Platform support.
66
+ detect_bind() {
67
+ if [ "$(uname -s)" = "Darwin" ]; then
68
+ printf '127.0.0.1\n'
69
+ return 0
70
+ fi
71
+ case "$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)" in
72
+ *"Docker Desktop"*)
73
+ printf '127.0.0.1\n'
74
+ return 0
75
+ ;;
76
+ esac
77
+ # println each gateway on its own line, then take the first IPv4 -- an
78
+ # IPv6-enabled bridge would otherwise concatenate the v4 and v6 gateways into
79
+ # one malformed token that ncat cannot bind.
80
+ gateway_ip="$(docker network inspect bridge --format '{{range .IPAM.Config}}{{println .Gateway}}{{end}}' 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true)"
81
+ if [ -n "$gateway_ip" ]; then
82
+ printf '%s\n' "$gateway_ip"
83
+ return 0
84
+ fi
85
+ return 1
86
+ }
87
+ # Preflight: auto-detecting the bind address shells out to the docker CLI. Skip
88
+ # the check when the bind is supplied explicitly, or on macOS (always loopback).
89
+ if [ -z "${CHROME_DEBUG_BIND:-}" ] && [ "$(uname -s)" != "Darwin" ] && ! command -v docker >/dev/null 2>&1; then
90
+ echo "chrome-host: error: auto-detecting the CDP bind address needs the 'docker' CLI, but it is" >&2
91
+ echo "chrome-host: not installed. Install Docker, or set CHROME_DEBUG_BIND (e.g. 127.0.0.1 for" >&2
92
+ echo "chrome-host: Docker Desktop) to skip detection." >&2
93
+ exit 1
94
+ fi
95
+ if [ -n "${CHROME_DEBUG_BIND:-}" ]; then
96
+ BIND="$CHROME_DEBUG_BIND"
97
+ else
98
+ BIND="$(detect_bind)" || true
99
+ if [ -z "$BIND" ]; then
100
+ echo "chrome-host: error: could not determine the Docker bridge gateway for the CDP forwarder" >&2
101
+ echo "chrome-host: (is the Docker daemon running?). Set CHROME_DEBUG_BIND to your docker bridge" >&2
102
+ echo "chrome-host: gateway IP to override, and retry." >&2
103
+ exit 1
104
+ fi
105
+ fi
106
+ case "$BIND" in
107
+ 127.0.0.1 | ::1) ;;
108
+ *)
109
+ echo "chrome-host: warning: native Docker detected — exposing the CDP port on $BIND so the" >&2
110
+ echo "chrome-host: container can reach it. This exposes browser control beyond" >&2
111
+ echo "chrome-host: loopback; firewall the port. Native Docker also needs" >&2
112
+ echo "chrome-host: 'host.docker.internal:host-gateway' in the node service's" >&2
113
+ echo "chrome-host: extra_hosts. Set CHROME_DEBUG_BIND to override." >&2
114
+ ;;
115
+ esac
116
+
117
+ # Preflight: a concrete non-loopback bind needs an ncat forwarder (see
118
+ # ensure_forwarder). Check ncat now, before launching Chrome, so a missing tool
119
+ # fails fast instead of orphaning a browser window. Skip it if a forwarder from
120
+ # a previous run is already serving the bind.
121
+ case "$BIND" in
122
+ 127.0.0.1 | ::1 | 0.0.0.0 | ::) ;;
123
+ *)
124
+ if ! curl -s -m 2 "http://${BIND}:${PORT}/json/version" >/dev/null 2>&1 \
125
+ && ! command -v ncat >/dev/null 2>&1; then
126
+ echo "chrome-host: error: this run must expose Chrome's CDP port ${PORT} on ${BIND} via an" >&2
127
+ echo "chrome-host: ncat forwarder, but 'ncat' is not installed. Install ncat (the 'nmap-ncat'" >&2
128
+ echo "chrome-host: or 'nmap' package, depending on your distro)." >&2
129
+ exit 1
130
+ fi
131
+ ;;
132
+ esac
133
+
134
+ # Profile is always per-project and not configurable. COMPOSE_PROJECT_NAME comes
135
+ # from .env (or your shell); fall back to the repo directory name, like Compose.
136
+ PROJECT="${COMPOSE_PROJECT_NAME:-$(basename "$env_root")}"
137
+ PROFILE="$HOME/.chrome-debug-profile-$PROJECT"
138
+
139
+ find_chrome() {
140
+ if [ -n "${CHROME_BIN:-}" ]; then
141
+ printf '%s\n' "$CHROME_BIN"
142
+ return 0
143
+ fi
144
+ case "$(uname -s)" in
145
+ Darwin)
146
+ for candidate in \
147
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
148
+ "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta" \
149
+ "/Applications/Chromium.app/Contents/MacOS/Chromium" \
150
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"; do
151
+ if [ -x "$candidate" ]; then
152
+ printf '%s\n' "$candidate"
153
+ return 0
154
+ fi
155
+ done
156
+ ;;
157
+ *)
158
+ for candidate in google-chrome google-chrome-stable chromium chromium-browser microsoft-edge brave-browser; do
159
+ resolved="$(command -v "$candidate" 2>/dev/null || true)"
160
+ if [ -n "$resolved" ]; then
161
+ printf '%s\n' "$resolved"
162
+ return 0
163
+ fi
164
+ done
165
+ ;;
166
+ esac
167
+ return 1
168
+ }
169
+
170
+ # Headed Chromium pins DevTools to 127.0.0.1 and silently ignores
171
+ # --remote-debugging-address (Fedora's build, among others), so the launcher no
172
+ # longer passes that flag: Chrome always binds loopback, and ensure_forwarder
173
+ # spawns an ncat hop on $BIND:$PORT to expose it for the container. Loopback and
174
+ # wildcard binds need no hop -- loopback is already reachable, and a wildcard
175
+ # would collide with Chrome's own loopback port.
176
+ ensure_forwarder() {
177
+ bind=$1
178
+ port=$2
179
+ pidfile=$3
180
+
181
+ case "$bind" in 127.0.0.1 | ::1 | 0.0.0.0 | ::) return 0 ;; esac
182
+
183
+ mkdir -p "$(dirname "$pidfile")"
184
+
185
+ # A reachable bind:port means a forwarder from a previous run still serves it
186
+ # (Chrome itself only binds loopback), so reuse it.
187
+ if curl -s -m 2 "http://${bind}:${port}/json/version" >/dev/null 2>&1; then
188
+ echo "chrome-host: forwarder already serving ${bind}:${port}"
189
+ return 0
190
+ fi
191
+
192
+ if ! command -v ncat >/dev/null 2>&1; then
193
+ echo "chrome-host: error: Chrome bound CDP to loopback only and 'ncat' is not installed." >&2
194
+ echo "chrome-host: install ncat (the 'nmap-ncat' or 'nmap' package, depending on your distro)" >&2
195
+ echo "chrome-host: so a host-side forwarder can expose ${port} on ${bind} for the container." >&2
196
+ return 1
197
+ fi
198
+
199
+ if [ -f "$pidfile" ]; then
200
+ kill "$(cat "$pidfile")" 2>/dev/null || true
201
+ rm -f "$pidfile"
202
+ fi
203
+
204
+ log=${pidfile%.pid}.log
205
+ nohup ncat -k -l "$bind" "$port" --sh-exec "ncat 127.0.0.1 $port" >"$log" 2>&1 &
206
+ echo $! >"$pidfile"
207
+
208
+ j=0
209
+ while [ "$j" -lt 20 ]; do
210
+ if curl -s -m 2 "http://${bind}:${port}/json/version" >/dev/null 2>&1; then
211
+ echo "chrome-host: forwarder ${bind}:${port} -> 127.0.0.1:${port} (pid $(cat "$pidfile"))"
212
+ return 0
213
+ fi
214
+ j=$((j + 1))
215
+ sleep 0.5
216
+ done
217
+
218
+ echo "chrome-host: error: forwarder failed to bind ${bind}:${port}; see $log" >&2
219
+ return 1
220
+ }
221
+
222
+ if curl -s -m 2 "http://127.0.0.1:${PORT}/json/version" >/dev/null 2>&1; then
223
+ echo "chrome-host: debug Chrome already running on 127.0.0.1:${PORT}"
224
+ ensure_forwarder "$BIND" "$PORT" "$PROFILE/forwarder.pid" || exit 1
225
+ exit 0
226
+ fi
227
+
228
+ CHROME="$(find_chrome || true)"
229
+ if [ -z "${CHROME:-}" ]; then
230
+ echo "chrome-host: error: no Chrome/Chromium found. Set CHROME_BIN=/path/to/browser and retry." >&2
231
+ exit 1
232
+ fi
233
+
234
+ mkdir -p "$PROFILE"
235
+
236
+ # Build the argument list (quote the glob so the shell doesn't expand '*').
237
+ # --ignore-certificate-errors skips the "Your connection is not private"
238
+ # interstitial for local HTTPS dev servers with a self-signed/absent cert.
239
+ # Acceptable here: this is a dedicated, isolated debug profile, not your
240
+ # everyday browser.
241
+ set -- \
242
+ --remote-debugging-port="$PORT" \
243
+ '--remote-allow-origins=*' \
244
+ --user-data-dir="$PROFILE" \
245
+ --no-first-run \
246
+ --no-default-browser-check \
247
+ --ignore-certificate-errors
248
+ set -- "$@" --new-window "http://localhost:${COMPOSE_DOCS_SERVER_PORT:-8000}"
249
+
250
+ nohup "$CHROME" "$@" >"$PROFILE/chrome-debug.log" 2>&1 &
251
+
252
+ i=0
253
+ while [ "$i" -lt 30 ]; do
254
+ if curl -s -m 2 "http://127.0.0.1:${PORT}/json/version" >/dev/null 2>&1; then
255
+ echo "chrome-host: debug Chrome ready on 127.0.0.1:${PORT}"
256
+ echo "chrome-host: binary: $CHROME"
257
+ echo "chrome-host: profile: $PROFILE"
258
+ echo "chrome-host: log: $PROFILE/chrome-debug.log"
259
+ ensure_forwarder "$BIND" "$PORT" "$PROFILE/forwarder.pid" || exit 1
260
+ exit 0
261
+ fi
262
+ i=$((i + 1))
263
+ sleep 0.5
264
+ done
265
+
266
+ echo "chrome-host: error: Chrome did not expose CDP on 127.0.0.1:${PORT}; see $PROFILE/chrome-debug.log" >&2
267
+ exit 1
@@ -0,0 +1,15 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ # File to store the hash of the package-lock.json
6
+ LOCK_HASH_FILE="node_modules/.package-lock-hash"
7
+
8
+ # Parent directory of the script
9
+ SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
10
+
11
+ # Change to the parent directory of the script
12
+ cd "$SCRIPT_DIR/.."
13
+
14
+ # Record the hash of the lockfile we just installed against
15
+ sha256sum package-lock.json | awk '{print $1}' > "$LOCK_HASH_FILE"
package/setup.sh ADDED
@@ -0,0 +1,107 @@
1
+ #!/bin/bash
2
+
3
+ DEFAULT_PROJECT_NAME="react-ui"
4
+ DEFAULT_USER_ID=1000
5
+ DEFAULT_GROUP_ID=1000
6
+
7
+ set -e
8
+ trap 'echo "Failed to set up project"; rm -f .env.temp; exit 1' ERR
9
+
10
+ # Function to handle sed command with cross-platform compatibility
11
+ sed_cmd() {
12
+ if [[ "$OSTYPE" == "darwin"* ]]; then
13
+ sed -i '' "$@"
14
+ else
15
+ sed -i "$@"
16
+ fi
17
+ }
18
+
19
+ cd "$(dirname "$0")"
20
+
21
+ echo "Setting up project..."
22
+
23
+ # Create and configure .env file if it doesn't exist
24
+ if [ ! -f .env ]; then
25
+ echo "Creating .env file..."
26
+ cp .env.dist .env.temp
27
+
28
+ echo "Configuring .env file..."
29
+
30
+ PROJECT_PATH=$(pwd)
31
+ PROJECT_NAME=$(basename "$PROJECT_PATH")
32
+
33
+ if [[ "$OSTYPE" == "darwin"* ]]; then
34
+ # MacOS
35
+ USER_ID=$DEFAULT_USER_ID
36
+ GROUP_ID=$DEFAULT_GROUP_ID
37
+ else
38
+ # Linux
39
+ USER_ID=$(id -u)
40
+ GROUP_ID=$(id -g)
41
+ fi
42
+
43
+ sed_cmd "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=$PROJECT_NAME|" .env.temp
44
+ sed_cmd "s|^COMPOSE_UID=.*|COMPOSE_UID=$USER_ID|" .env.temp
45
+ sed_cmd "s|^COMPOSE_GID=.*|COMPOSE_GID=$GROUP_ID|" .env.temp
46
+
47
+ LOCAL_SHELL=
48
+ if [ -n "$SHELL" ]; then
49
+ sed_cmd "s|^SHELL=.*|SHELL=$SHELL|" .env.temp
50
+ LOCAL_SHELL=$SHELL
51
+ fi
52
+
53
+ LOCAL_EDITOR=
54
+ if [ -n "$EDITOR" ]; then
55
+ sed_cmd "s|^EDITOR=.*|EDITOR=$EDITOR|" .env.temp
56
+ LOCAL_EDITOR=$EDITOR
57
+ fi
58
+
59
+ LOCAL_VISUAL=
60
+ if [ -n "$VISUAL" ]; then
61
+ sed_cmd "s|^VISUAL=.*|VISUAL=$VISUAL|" .env.temp
62
+ LOCAL_VISUAL=$VISUAL
63
+ fi
64
+
65
+ cp .env.temp .env
66
+ rm .env.temp
67
+
68
+ echo "Configured .env file with the following values:"
69
+ echo "Project name: $PROJECT_NAME"
70
+ echo "Project path: $PROJECT_PATH"
71
+ echo "User ID: $USER_ID"
72
+ echo "Group ID: $GROUP_ID"
73
+
74
+ if [ -n "$LOCAL_SHELL" ]; then
75
+ echo "Shell: $LOCAL_SHELL"
76
+ fi
77
+ if [ -n "$LOCAL_EDITOR" ]; then
78
+ echo "Editor: $LOCAL_EDITOR"
79
+ fi
80
+ if [ -n "$LOCAL_VISUAL" ]; then
81
+ echo "Visual: $LOCAL_VISUAL"
82
+ fi
83
+ else
84
+ echo ".env file already exists, skipping creation."
85
+ fi
86
+
87
+ # Create docker-compose.yml if it doesn't exist
88
+ if [ ! -f docker-compose.yml ]; then
89
+ echo "Creating and configuring docker-compose.yml file..."
90
+ cp docker-compose.yml.dist docker-compose.yml
91
+
92
+ DEFAULT_PROJECT_DEVCONTAINER_IMAGE="${DEFAULT_PROJECT_NAME}_devcontainer"
93
+ PROJECT_NAME=$(grep -E '^COMPOSE_PROJECT_NAME=' .env | cut -d '=' -f 2-)
94
+ PROJECT_DEVCONTAINER_IMAGE="${PROJECT_NAME}_devcontainer"
95
+
96
+ sed_cmd "s|image: $DEFAULT_PROJECT_DEVCONTAINER_IMAGE|image: $PROJECT_DEVCONTAINER_IMAGE|" docker-compose.yml
97
+ else
98
+ echo "docker-compose.yml file already exists, skipping creation."
99
+ fi
100
+
101
+ # Configure the Chrome-host bridge networking (native Docker Engine only)
102
+ sh ./scripts/mcps/chrome-host/mcp-setup.sh
103
+
104
+ # Build Docker images
105
+ sh ./docker/build-docker-images.sh
106
+
107
+ echo "Project setup completed successfully!"
@@ -8,6 +8,7 @@ import { getRootPriorityClassName } from '../_helpers/getRootPriorityClassName';
8
8
  import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
9
9
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
10
10
  import { ButtonGroupContext } from '../ButtonGroup';
11
+ import { FormLayoutContext } from '../FormLayout';
11
12
  import { InputGroupContext } from '../InputGroup/InputGroupContext';
12
13
  import getRootLabelVisibilityClassName from './helpers/getRootLabelVisibilityClassName';
13
14
  import styles from './Button.module.scss';
@@ -30,6 +31,7 @@ export const Button = React.forwardRef((props, ref) => {
30
31
  ...restProps
31
32
  } = props;
32
33
  const buttonGroupContext = useContext(ButtonGroupContext);
34
+ const formLayoutContext = useContext(FormLayoutContext);
33
35
  const inputGroupContext = useContext(InputGroupContext);
34
36
 
35
37
  if (buttonGroupContext && inputGroupContext) {
@@ -58,6 +60,8 @@ export const Button = React.forwardRef((props, ref) => {
58
60
  resolveContextOrProp(buttonGroupContext && buttonGroupContext.block, block) && styles.isRootBlock,
59
61
  buttonGroupContext && styles.isRootInButtonGroup,
60
62
  inputGroupContext && styles.isRootInInputGroup,
63
+ formLayoutContext && styles.isRootInFormLayout,
64
+ formLayoutContext && formLayoutContext.layout === 'horizontal' && styles.isRootLayoutHorizontal,
61
65
  feedbackIcon && styles.hasRootFeedback,
62
66
  )}
63
67
  disabled={resolveContextOrProp(primaryContext && primaryContext.disabled, disabled) || !!feedbackIcon}
@@ -2,6 +2,7 @@
2
2
  // borders.
3
3
 
4
4
  @use "sass:map";
5
+ @use "../../styles/settings/forms";
5
6
  @use "../../styles/tools/breakpoint";
6
7
  @use "../../styles/tools/collections";
7
8
  @use "settings";
@@ -78,6 +79,16 @@
78
79
  color: transparent;
79
80
  }
80
81
 
82
+ .isRootInFormLayout {
83
+ justify-self: start;
84
+ }
85
+
86
+ .isRootLayoutHorizontal {
87
+ @include breakpoint.up(forms.$horizontal-breakpoint) {
88
+ grid-column-start: 2;
89
+ }
90
+ }
91
+
81
92
  .isRootInButtonGroup,
82
93
  .isRootInInputGroup {
83
94
  z-index: map.get(settings.$group-z-indexes, button);
@@ -16,7 +16,10 @@ import { getRootValidationStateClassName } from '../_helpers/getRootValidationSt
16
16
  import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
17
17
  import { InputGroupContext } from '../InputGroup';
18
18
  import { Text } from '../Text';
19
- import { FormLayoutContext } from '../FormLayout';
19
+ import {
20
+ FormLayoutContext,
21
+ FormLayoutCustomFieldContext,
22
+ } from '../FormLayout';
20
23
  import styles from './FileInputField.module.scss';
21
24
 
22
25
  /* istanbul ignore file */
@@ -40,6 +43,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
40
43
  } = props;
41
44
 
42
45
  const formLayoutContext = useContext(FormLayoutContext);
46
+ const formLayoutCustomFieldContext = useContext(FormLayoutCustomFieldContext);
43
47
  const inputGroupContext = useContext(InputGroupContext);
44
48
  const translations = useContext(TranslationsContext);
45
49
 
@@ -172,7 +176,7 @@ export const FileInputField = React.forwardRef((props, ref) => {
172
176
  <label
173
177
  className={classNames(
174
178
  styles.label,
175
- (!isLabelVisible || inputGroupContext) && styles.isLabelHidden,
179
+ (!isLabelVisible || inputGroupContext || formLayoutCustomFieldContext) && styles.isLabelHidden,
176
180
  )}
177
181
  htmlFor={id}
178
182
  id={`${id}__labelText`}
@@ -286,6 +290,9 @@ FileInputField.propTypes = {
286
290
  /**
287
291
  * If `false`, the label will be visually hidden (but remains accessible by assistive
288
292
  * technologies).
293
+ *
294
+ * Automatically set to `false` when the component is rendered within `FormLayoutCustomField`
295
+ * component.
289
296
  */
290
297
  isLabelVisible: PropTypes.bool,
291
298
  /**
@@ -7,6 +7,7 @@ import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
7
  import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
8
  import { isChildrenEmpty } from '../../helpers/isChildrenEmpty/isChildrenEmpty';
9
9
  import { FormLayoutContext } from './FormLayoutContext';
10
+ import { FormLayoutCustomFieldContext } from './FormLayoutCustomFieldContext';
10
11
  import styles from './FormLayoutCustomField.module.scss';
11
12
 
12
13
  const renderLabel = (id, label, labelForId) => {
@@ -73,7 +74,9 @@ export const FormLayoutCustomField = ({
73
74
  className={styles.field}
74
75
  id={id && `${id}__field`}
75
76
  >
76
- {children}
77
+ <FormLayoutCustomFieldContext.Provider value>
78
+ {children}
79
+ </FormLayoutCustomFieldContext.Provider>
77
80
  </div>
78
81
  </div>
79
82
  );
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+
3
+ export const FormLayoutCustomFieldContext = React.createContext(null);