@volksverpetzer/ui-web 0.9.0 → 0.10.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/dist/InputButton.css +25 -0
- package/dist/InputButton.d.ts +22 -0
- package/dist/InputButton.js +37 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +26 -0
- package/package.json +2 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.vvp-ui-input-button {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: block;
|
|
4
|
+
width: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Compound selectors (rather than relying on file load order) so these
|
|
9
|
+
* reliably win over Input's and Button's own same-specificity base rules
|
|
10
|
+
* regardless of how the package's CSS files get concatenated.
|
|
11
|
+
*/
|
|
12
|
+
.vvp-ui-input-button__field.vvp-ui-input {
|
|
13
|
+
padding-right: 7rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.vvp-ui-input-button__action.vvp-ui-btn {
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 2px;
|
|
19
|
+
right: 2px;
|
|
20
|
+
bottom: 2px;
|
|
21
|
+
border-top-left-radius: 0;
|
|
22
|
+
border-bottom-left-radius: 0;
|
|
23
|
+
border-top-right-radius: var(--vvp-radius-md, 12px);
|
|
24
|
+
border-bottom-right-radius: var(--vvp-radius-md, 12px);
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import "./InputButton.css";
|
|
3
|
+
export interface InputButtonProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "children"> {
|
|
4
|
+
/** Rendered inside the embedded action button, e.g. "Shorten". */
|
|
5
|
+
buttonLabel: ReactNode;
|
|
6
|
+
/** Disables just the action button — the input stays interactive (e.g. while a submission triggered by it is in flight). */
|
|
7
|
+
buttonDisabled?: boolean;
|
|
8
|
+
/** aria-label for the action button, for icon-only labels. */
|
|
9
|
+
buttonAriaLabel?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A text input with a primary action button embedded in its right edge —
|
|
13
|
+
* the "paste a value and go" pattern (a URL shortener, an email signup
|
|
14
|
+
* field). Composes `Input` and `Button` (`variant="primary"`, `size="sm"`,
|
|
15
|
+
* `type="submit"`) rather than inventing new styling.
|
|
16
|
+
*
|
|
17
|
+
* The button's own rendered width is measured and applied as the input's
|
|
18
|
+
* right padding, so a longer label (e.g. a "Shortening…" loading state)
|
|
19
|
+
* never overlaps typed text. A fixed fallback covers the brief window
|
|
20
|
+
* before that measurement lands (first paint, or SSR).
|
|
21
|
+
*/
|
|
22
|
+
export declare const InputButton: import("react").ForwardRefExoticComponent<InputButtonProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useLayoutEffect, useRef, useState, } from "react";
|
|
3
|
+
import "./InputButton.css";
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
import { Input } from "./Input";
|
|
6
|
+
// useLayoutEffect warns when it runs during SSR (no DOM to measure yet);
|
|
7
|
+
// useEffect is a no-op there and picks up the real measurement on mount.
|
|
8
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
9
|
+
/**
|
|
10
|
+
* A text input with a primary action button embedded in its right edge —
|
|
11
|
+
* the "paste a value and go" pattern (a URL shortener, an email signup
|
|
12
|
+
* field). Composes `Input` and `Button` (`variant="primary"`, `size="sm"`,
|
|
13
|
+
* `type="submit"`) rather than inventing new styling.
|
|
14
|
+
*
|
|
15
|
+
* The button's own rendered width is measured and applied as the input's
|
|
16
|
+
* right padding, so a longer label (e.g. a "Shortening…" loading state)
|
|
17
|
+
* never overlaps typed text. A fixed fallback covers the brief window
|
|
18
|
+
* before that measurement lands (first paint, or SSR).
|
|
19
|
+
*/
|
|
20
|
+
export const InputButton = forwardRef(function InputButton({ buttonLabel, buttonDisabled, buttonAriaLabel, style, ...rest }, ref) {
|
|
21
|
+
const buttonRef = useRef(null);
|
|
22
|
+
const [buttonWidth, setButtonWidth] = useState(0);
|
|
23
|
+
useIsomorphicLayoutEffect(() => {
|
|
24
|
+
const button = buttonRef.current;
|
|
25
|
+
if (!button)
|
|
26
|
+
return;
|
|
27
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
28
|
+
setButtonWidth(entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width);
|
|
29
|
+
});
|
|
30
|
+
observer.observe(button);
|
|
31
|
+
return () => observer.disconnect();
|
|
32
|
+
}, []);
|
|
33
|
+
return (_jsxs("div", { className: "vvp-ui-input-button", children: [_jsx(Input, { ref: ref, className: "vvp-ui-input-button__field", style: {
|
|
34
|
+
...style,
|
|
35
|
+
paddingRight: buttonWidth ? buttonWidth + 8 : undefined,
|
|
36
|
+
}, ...rest }), _jsx(Button, { ref: buttonRef, type: "submit", size: "sm", disabled: buttonDisabled, "aria-label": buttonAriaLabel, className: "vvp-ui-input-button__action", children: buttonLabel })] }));
|
|
37
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -7,3 +7,4 @@ export { Input, type InputProps } from "./Input";
|
|
|
7
7
|
export { Alert, type AlertProps, type AlertVariant } from "./Alert";
|
|
8
8
|
export { Slider, type SliderProps } from "./Slider";
|
|
9
9
|
export { ThemeToggle } from "./ThemeToggle";
|
|
10
|
+
export { InputButton, type InputButtonProps } from "./InputButton";
|
package/dist/index.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -217,6 +217,32 @@
|
|
|
217
217
|
outline-color: var(--vvp-error, #c62828);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
.vvp-ui-input-button {
|
|
221
|
+
position: relative;
|
|
222
|
+
display: block;
|
|
223
|
+
width: 100%;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* Compound selectors (rather than relying on file load order) so these
|
|
228
|
+
* reliably win over Input's and Button's own same-specificity base rules
|
|
229
|
+
* regardless of how the package's CSS files get concatenated.
|
|
230
|
+
*/
|
|
231
|
+
.vvp-ui-input-button__field.vvp-ui-input {
|
|
232
|
+
padding-right: 7rem;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.vvp-ui-input-button__action.vvp-ui-btn {
|
|
236
|
+
position: absolute;
|
|
237
|
+
top: 2px;
|
|
238
|
+
right: 2px;
|
|
239
|
+
bottom: 2px;
|
|
240
|
+
border-top-left-radius: 0;
|
|
241
|
+
border-bottom-left-radius: 0;
|
|
242
|
+
border-top-right-radius: var(--vvp-radius-md, 12px);
|
|
243
|
+
border-bottom-right-radius: var(--vvp-radius-md, 12px);
|
|
244
|
+
}
|
|
245
|
+
|
|
220
246
|
/*
|
|
221
247
|
* LinkButton's fixed look: a subtle bordered "pill card" with a soft
|
|
222
248
|
* drop shadow, distinct from Button's ghost variant (see LinkButton.tsx
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volksverpetzer/ui-web",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert, Slider, ThemeToggle) for vvp_link_shortener, vvp_divi5_extensions, crowdfunding, and vectorcrawl. Plain, hand-scoped CSS (vvp-ui-* class prefix) consuming the --vvp-* custom properties emitted by @volksverpetzer/design-tokens — no Tailwind classes, so it drops into a Tailwind app and Divi's WordPress-embedded CSS alike.",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Shared brand-visible React components (Button, Badge, Card, ProgressBar, Input, Alert, Slider, ThemeToggle, InputButton) for vvp_link_shortener, vvp_divi5_extensions, crowdfunding, and vectorcrawl. Plain, hand-scoped CSS (vvp-ui-* class prefix) consuming the --vvp-* custom properties emitted by @volksverpetzer/design-tokens — no Tailwind classes, so it drops into a Tailwind app and Divi's WordPress-embedded CSS alike.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|