ionbase-ui 0.8.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/components/Input.d.ts +16 -0
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/Input.js +6 -3
- package/dist/components/Input.js.map +1 -1
- package/dist/components/Link.d.ts +49 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +77 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/PhoneInput.d.ts +44 -0
- package/dist/components/PhoneInput.d.ts.map +1 -0
- package/dist/components/PhoneInput.js +67 -0
- package/dist/components/PhoneInput.js.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/styles/index.css +2 -0
- package/dist/styles/input.css +19 -0
- package/dist/styles/link.css +181 -0
- package/dist/styles/phone-input.css +131 -0
- package/package.json +1 -1
|
@@ -26,6 +26,22 @@ export interface InputProps extends AriaTextFieldProps, InputDOMProps {
|
|
|
26
26
|
leadingIcon?: React.ReactNode;
|
|
27
27
|
/** Trailing icon. Figma's `Show Trailing Icon` + `Trailing Icon` swap. */
|
|
28
28
|
trailingIcon?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* A segment butted against the left of the control, sharing its outline —
|
|
31
|
+
* Figma's `Input/Phone` (80:372) country-code block.
|
|
32
|
+
*
|
|
33
|
+
* Distinct from `leadingIcon`, which sits *inside* the box: an addon has its
|
|
34
|
+
* own surface and border and is a sibling of the control, so the two cannot
|
|
35
|
+
* be the same slot. It lives here rather than in `PhoneInput` because the
|
|
36
|
+
* `.ion-field` wrapper has to enclose the addon and the control together —
|
|
37
|
+
* building it outside would put the label above only half the control and
|
|
38
|
+
* break the `aria-labelledby` wiring `useTextField` sets up.
|
|
39
|
+
*
|
|
40
|
+
* Phone is the only addon Figma specifies. This is a slot, not a system:
|
|
41
|
+
* resist growing a trailing counterpart or an addon variant ladder until a
|
|
42
|
+
* design actually calls for one.
|
|
43
|
+
*/
|
|
44
|
+
leadingAddon?: React.ReactNode;
|
|
29
45
|
/**
|
|
30
46
|
* @deprecated Use `isDisabled`. Accepted as an alias for one minor version.
|
|
31
47
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../src/components/Input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C;;;;;;;;;GASG;AACH,KAAK,aAAa,GAAG,IAAI,CACvB,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAC3C,MAAM,kBAAkB,GAAG,MAAM,GAAG,gBAAgB,GAAG,UAAU,CAClE,CAAC;AAEF,MAAM,WAAW,UAAW,SAAQ,kBAAkB,EAAE,aAAa;IACnE,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,CAAC;IAC9E,uEAAuE;IACvE,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../src/components/Input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C;;;;;;;;;GASG;AACH,KAAK,aAAa,GAAG,IAAI,CACvB,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAC3C,MAAM,kBAAkB,GAAG,MAAM,GAAG,gBAAgB,GAAG,UAAU,CAClE,CAAC;AAEF,MAAM,WAAW,UAAW,SAAQ,kBAAkB,EAAE,aAAa;IACnE,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,CAAC;IAC9E,uEAAuE;IACvE,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,KAAK,qFAmIjB,CAAC"}
|
package/dist/components/Input.js
CHANGED
|
@@ -22,7 +22,7 @@ import { resolveDisabled } from './resolve-disabled.js';
|
|
|
22
22
|
* expresses; Default, Invalid, Disabled and Read-only are props.
|
|
23
23
|
*/
|
|
24
24
|
export const Input = forwardRef((props, forwardedRef) => {
|
|
25
|
-
const { size = 'md', leadingIcon, trailingIcon, className: customClassName, wrapperClassName, label, description, errorMessage, isDisabled: isDisabledProp, disabled, isReadOnly, isInvalid, ...restProps } = props;
|
|
25
|
+
const { size = 'md', leadingIcon, trailingIcon, leadingAddon, className: customClassName, wrapperClassName, label, description, errorMessage, isDisabled: isDisabledProp, disabled, isReadOnly, isInvalid, ...restProps } = props;
|
|
26
26
|
const isDisabled = resolveDisabled(isDisabledProp, disabled);
|
|
27
27
|
const ariaProps = { ...props, isDisabled };
|
|
28
28
|
/*
|
|
@@ -64,19 +64,22 @@ export const Input = forwardRef((props, forwardedRef) => {
|
|
|
64
64
|
'data-disabled': isDisabled || undefined,
|
|
65
65
|
};
|
|
66
66
|
const box = (_jsxs("div", { ...hoverProps, ...stateAttributes, className: boxClassNames, children: [leadingIcon && (_jsx("span", { className: "ion-input__icon-start", "aria-hidden": "true", children: leadingIcon })), _jsx("input", { ...domProps, ...mergeProps(inputProps, focusProps), ref: domRef, className: "ion-input__field" }), trailingIcon && (_jsx("span", { className: "ion-input__icon-end", "aria-hidden": "true", children: trailingIcon }))] }));
|
|
67
|
+
// The addon and the control share one outline, so they need a row of their
|
|
68
|
+
// own — `.ion-field` is a column and would stack them.
|
|
69
|
+
const control = leadingAddon ? (_jsxs("div", { className: "ion-input-group", children: [leadingAddon, box] })) : (box);
|
|
67
70
|
// Figma's Error variant recolours the helper text, so the error message
|
|
68
71
|
// takes the helper's slot rather than stacking below it.
|
|
69
72
|
const helper = isInvalid && errorMessage ? errorMessage : description;
|
|
70
73
|
const helperProps = isInvalid && errorMessage ? errorMessageProps : descriptionProps;
|
|
71
74
|
if (!label && !helper)
|
|
72
|
-
return
|
|
75
|
+
return control;
|
|
73
76
|
return (_jsxs("div", { className: [
|
|
74
77
|
'ion-field',
|
|
75
78
|
isInvalid ? 'ion-field--error' : '',
|
|
76
79
|
wrapperClassName || '',
|
|
77
80
|
]
|
|
78
81
|
.filter(Boolean)
|
|
79
|
-
.join(' '), children: [label && (_jsx("label", { ...labelProps, className: "ion-field__label", children: label })),
|
|
82
|
+
.join(' '), children: [label && (_jsx("label", { ...labelProps, className: "ion-field__label", children: label })), control, helper && (_jsx("span", { ...helperProps, className: "ion-field__helper", children: helper }))] }));
|
|
80
83
|
});
|
|
81
84
|
Input.displayName = 'Input';
|
|
82
85
|
//# sourceMappingURL=Input.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/components/Input.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAc,EAAE,UAAU,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE9E,OAAO,EAAE,6BAA6B,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/components/Input.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AriaLinkOptions } from 'react-aria';
|
|
3
|
+
export type LinkVariant = 'inline' | 'standalone';
|
|
4
|
+
/**
|
|
5
|
+
* The plain anchor attributes React Aria has no opinion about — `target`,
|
|
6
|
+
* `rel`, `download`, `hreflang` and the rest. `AriaLinkOptions` wins every
|
|
7
|
+
* overlap, the same split `Input` makes against `AriaTextFieldProps`.
|
|
8
|
+
*/
|
|
9
|
+
type LinkDOMProps = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof AriaLinkOptions | 'color'>;
|
|
10
|
+
export interface LinkProps extends AriaLinkOptions, LinkDOMProps {
|
|
11
|
+
/**
|
|
12
|
+
* Matches the Figma `Type` variant.
|
|
13
|
+
*
|
|
14
|
+
* `inline` is for links inside body copy and is underlined in every state —
|
|
15
|
+
* a link in a paragraph must not rely on colour alone (WCAG 1.4.1).
|
|
16
|
+
* `standalone` is for links that already read as interactive from their
|
|
17
|
+
* position, and underlines on hover only.
|
|
18
|
+
*/
|
|
19
|
+
variant?: LinkVariant;
|
|
20
|
+
/** Optional icon before the label. */
|
|
21
|
+
startIcon?: React.ReactNode;
|
|
22
|
+
/** Optional icon after the label — the external-link or arrow affordance. */
|
|
23
|
+
endIcon?: React.ReactNode;
|
|
24
|
+
className?: string;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `isDisabled`. Accepted as an alias for one minor version.
|
|
28
|
+
*/
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Link — Figma `Link` (774:1516).
|
|
33
|
+
*
|
|
34
|
+
* Renders an `<a>` when given an `href` and a `<button>` otherwise, the same
|
|
35
|
+
* judgment `NavItem` makes: the element follows what the caller is actually
|
|
36
|
+
* building rather than a separate `as` prop. A link that does not navigate is
|
|
37
|
+
* a button, and shipping one as an anchor breaks middle-click, "open in new
|
|
38
|
+
* tab" and the screen-reader announcement all at once.
|
|
39
|
+
*
|
|
40
|
+
* NO SIZE PROP, DELIBERATELY. A link is an inline element and inherits its
|
|
41
|
+
* type from the text around it — `font-size: inherit` in the stylesheet, and
|
|
42
|
+
* icons sized in `em` so they scale with it. Figma has to draw its variants at
|
|
43
|
+
* some concrete size (16/24, `type/body`), but that is one sample of an
|
|
44
|
+
* inheriting element, not a specification. Do not add a size ladder to match
|
|
45
|
+
* the drawing.
|
|
46
|
+
*/
|
|
47
|
+
export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement & HTMLButtonElement>>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=Link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../src/components/Link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAQvE,OAAO,KAAK,EAAE,eAAe,EAAqB,MAAM,YAAY,CAAC;AAGrE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC;AAElD;;;;GAIG;AACH,KAAK,YAAY,GAAG,IAAI,CACtB,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,MAAM,eAAe,GAAG,OAAO,CAChC,CAAC;AAEF,MAAM,WAAW,SAAU,SAAQ,eAAe,EAAE,YAAY;IAC9D;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,sCAAsC;IACtC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,IAAI,yGAyHf,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useRef, useImperativeHandle } from 'react';
|
|
4
|
+
import { useLink, useButton, useHover, useFocusRing, mergeProps, } from 'react-aria';
|
|
5
|
+
import { resolveDisabled } from './resolve-disabled.js';
|
|
6
|
+
/**
|
|
7
|
+
* Link — Figma `Link` (774:1516).
|
|
8
|
+
*
|
|
9
|
+
* Renders an `<a>` when given an `href` and a `<button>` otherwise, the same
|
|
10
|
+
* judgment `NavItem` makes: the element follows what the caller is actually
|
|
11
|
+
* building rather than a separate `as` prop. A link that does not navigate is
|
|
12
|
+
* a button, and shipping one as an anchor breaks middle-click, "open in new
|
|
13
|
+
* tab" and the screen-reader announcement all at once.
|
|
14
|
+
*
|
|
15
|
+
* NO SIZE PROP, DELIBERATELY. A link is an inline element and inherits its
|
|
16
|
+
* type from the text around it — `font-size: inherit` in the stylesheet, and
|
|
17
|
+
* icons sized in `em` so they scale with it. Figma has to draw its variants at
|
|
18
|
+
* some concrete size (16/24, `type/body`), but that is one sample of an
|
|
19
|
+
* inheriting element, not a specification. Do not add a size ladder to match
|
|
20
|
+
* the drawing.
|
|
21
|
+
*/
|
|
22
|
+
export const Link = forwardRef((props, forwardedRef) => {
|
|
23
|
+
const { variant = 'inline', startIcon, endIcon, className: customClassName, children, isDisabled: isDisabledProp, disabled, href, ...restProps } = props;
|
|
24
|
+
const isDisabled = resolveDisabled(isDisabledProp, disabled);
|
|
25
|
+
const domRef = useRef(null);
|
|
26
|
+
useImperativeHandle(forwardedRef, () => domRef.current);
|
|
27
|
+
const isNavigating = Boolean(href);
|
|
28
|
+
/*
|
|
29
|
+
* `useLink` for the anchor, `useButton` for the button — NOT `useLink` for
|
|
30
|
+
* both.
|
|
31
|
+
*
|
|
32
|
+
* `useLink` on a non-anchor stamps `role="link"` on it, which is the correct
|
|
33
|
+
* behaviour for a hand-rolled link and precisely wrong here: this component
|
|
34
|
+
* renders a real `<button>` when there is nothing to navigate to, and a
|
|
35
|
+
* button announced as a link tells a screen-reader user to expect navigation
|
|
36
|
+
* that will not happen. Caught by a story asserting `getByRole('button')`.
|
|
37
|
+
*
|
|
38
|
+
* Both hooks are called unconditionally because React forbids conditional
|
|
39
|
+
* hooks and `href` can change between renders. Only one result is ever
|
|
40
|
+
* spread, and both deliver their behaviour entirely through the props they
|
|
41
|
+
* return, so the unused one attaches nothing to the DOM.
|
|
42
|
+
*/
|
|
43
|
+
const link = useLink({ ...props, isDisabled, elementType: 'a' }, domRef);
|
|
44
|
+
const button = useButton({
|
|
45
|
+
...props,
|
|
46
|
+
isDisabled,
|
|
47
|
+
elementType: 'button',
|
|
48
|
+
}, domRef);
|
|
49
|
+
const interactionProps = isNavigating ? link.linkProps : button.buttonProps;
|
|
50
|
+
const isPressed = isNavigating ? link.isPressed : button.isPressed;
|
|
51
|
+
const { hoverProps, isHovered } = useHover({ isDisabled });
|
|
52
|
+
const { focusProps, isFocusVisible } = useFocusRing();
|
|
53
|
+
const classNames = ['ion-link', `ion-link--${variant}`, customClassName || '']
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(' ');
|
|
56
|
+
// `undefined` rather than `false`, so the attribute is omitted entirely
|
|
57
|
+
// instead of rendering data-hovered="false" — which would still match an
|
|
58
|
+
// `[data-hovered]` selector.
|
|
59
|
+
const stateAttributes = {
|
|
60
|
+
'data-hovered': isHovered || undefined,
|
|
61
|
+
'data-pressed': isPressed || undefined,
|
|
62
|
+
'data-focused': isFocusVisible || undefined,
|
|
63
|
+
'data-disabled': isDisabled || undefined,
|
|
64
|
+
};
|
|
65
|
+
const content = (_jsxs(_Fragment, { children: [startIcon && (_jsx("span", { className: "ion-link__icon-start", "aria-hidden": "true", children: startIcon })), children && _jsx("span", { className: "ion-link__label", children: children }), endIcon && (_jsx("span", { className: "ion-link__icon-end", "aria-hidden": "true", children: endIcon }))] }));
|
|
66
|
+
if (!href) {
|
|
67
|
+
return (_jsx("button", { ...restProps, ...mergeProps(interactionProps, hoverProps, focusProps), ...stateAttributes, ref: domRef, type: "button", disabled: isDisabled, className: classNames, children: content }));
|
|
68
|
+
}
|
|
69
|
+
return (_jsx("a", { ...restProps, ...mergeProps(interactionProps, hoverProps, focusProps), ...stateAttributes, ref: domRef,
|
|
70
|
+
// A disabled link must not remain navigable. `aria-disabled` alone
|
|
71
|
+
// still leaves the href clickable and focusable, so the href is
|
|
72
|
+
// dropped and it is taken out of the tab order — the same treatment
|
|
73
|
+
// NavItem gives a disabled link.
|
|
74
|
+
href: isDisabled ? undefined : href, "aria-disabled": isDisabled || undefined, tabIndex: isDisabled ? -1 : undefined, className: classNames, children: content }));
|
|
75
|
+
});
|
|
76
|
+
Link.displayName = 'Link';
|
|
77
|
+
//# sourceMappingURL=Link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/components/Link.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type InputProps } from './Input.js';
|
|
3
|
+
export interface PhoneInputProps extends Omit<InputProps, 'leadingAddon' | 'leadingIcon'> {
|
|
4
|
+
/** The dial code shown in the leading block, e.g. `+1`. */
|
|
5
|
+
dialCode?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Accessible name for the dial-code trigger. It shows only `+1`, which names
|
|
8
|
+
* a value rather than an action, so the button needs a label of its own.
|
|
9
|
+
*/
|
|
10
|
+
countryLabel?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Spread onto the dial-code `<button>`. This is how a country picker gets
|
|
13
|
+
* attached — see the note on scope below.
|
|
14
|
+
*/
|
|
15
|
+
countryButtonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* PhoneInput — Figma `Input/Phone` (80:372).
|
|
19
|
+
*
|
|
20
|
+
* A dial-code block butted against an Input, sharing one outline: the block
|
|
21
|
+
* carries the left radii and the control the right, so the seam is square on
|
|
22
|
+
* both sides and reads as a single control. Both keep their full 1px border,
|
|
23
|
+
* which is what Figma draws — the 2px seam is the two strokes meeting, not an
|
|
24
|
+
* accident.
|
|
25
|
+
*
|
|
26
|
+
* Three sizes, no State axis. Every interaction state is the Input's, reached
|
|
27
|
+
* through the ordinary props, because Figma composes this from the same `Input`
|
|
28
|
+
* instance rather than redrawing it.
|
|
29
|
+
*
|
|
30
|
+
* WHAT THIS DELIBERATELY DOES NOT DO
|
|
31
|
+
*
|
|
32
|
+
* It does not pick countries. The chevron implies a menu and Figma specifies no
|
|
33
|
+
* open state for it — no list, no flags, no search, no selected state. Country
|
|
34
|
+
* data and the picker are application concerns with real editorial weight (which
|
|
35
|
+
* territories, which names, which order), and inventing them here would be
|
|
36
|
+
* designing rather than implementing.
|
|
37
|
+
*
|
|
38
|
+
* So the trigger is a real `<button>` with an accessible name, and
|
|
39
|
+
* `countryButtonProps` is how you wire it to a `Menu`, a popover or your own
|
|
40
|
+
* listbox — including the `aria-haspopup` and `aria-expanded` that only the
|
|
41
|
+
* thing owning the popup can set honestly.
|
|
42
|
+
*/
|
|
43
|
+
export declare const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
44
|
+
//# sourceMappingURL=PhoneInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PhoneInput.d.ts","sourceRoot":"","sources":["../../src/components/PhoneInput.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAwBpD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,UAAU,EACV,cAAc,GAAG,aAAa,CAC/B;IACC,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,UAAU,0FAqDtB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* No `'use client'` here, deliberately — the build asserts its absence.
|
|
4
|
+
*
|
|
5
|
+
* This component holds no state and calls no hook: it composes a button and
|
|
6
|
+
* `Input`, and `Input` carries the boundary already. Marking it would pull an
|
|
7
|
+
* otherwise server-renderable module out of the server graph for nothing.
|
|
8
|
+
*/
|
|
9
|
+
import { forwardRef } from 'react';
|
|
10
|
+
import { Input } from './Input.js';
|
|
11
|
+
import { resolveDisabled } from './resolve-disabled.js';
|
|
12
|
+
/**
|
|
13
|
+
* The chevron from Figma's `Chevron` slot, inlined.
|
|
14
|
+
*
|
|
15
|
+
* Same reasoning as Select's: the shape never varies and is part of the
|
|
16
|
+
* component rather than a slot a consumer fills, so drawing it here avoids
|
|
17
|
+
* making `ionbase-ui` depend on `ionbase-icons` — a dependency the package
|
|
18
|
+
* deliberately does not have. Sizing comes from the CSS, so it tracks the size
|
|
19
|
+
* modifier without a prop.
|
|
20
|
+
*/
|
|
21
|
+
const ChevronDown = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "m6 9 6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
22
|
+
/**
|
|
23
|
+
* PhoneInput — Figma `Input/Phone` (80:372).
|
|
24
|
+
*
|
|
25
|
+
* A dial-code block butted against an Input, sharing one outline: the block
|
|
26
|
+
* carries the left radii and the control the right, so the seam is square on
|
|
27
|
+
* both sides and reads as a single control. Both keep their full 1px border,
|
|
28
|
+
* which is what Figma draws — the 2px seam is the two strokes meeting, not an
|
|
29
|
+
* accident.
|
|
30
|
+
*
|
|
31
|
+
* Three sizes, no State axis. Every interaction state is the Input's, reached
|
|
32
|
+
* through the ordinary props, because Figma composes this from the same `Input`
|
|
33
|
+
* instance rather than redrawing it.
|
|
34
|
+
*
|
|
35
|
+
* WHAT THIS DELIBERATELY DOES NOT DO
|
|
36
|
+
*
|
|
37
|
+
* It does not pick countries. The chevron implies a menu and Figma specifies no
|
|
38
|
+
* open state for it — no list, no flags, no search, no selected state. Country
|
|
39
|
+
* data and the picker are application concerns with real editorial weight (which
|
|
40
|
+
* territories, which names, which order), and inventing them here would be
|
|
41
|
+
* designing rather than implementing.
|
|
42
|
+
*
|
|
43
|
+
* So the trigger is a real `<button>` with an accessible name, and
|
|
44
|
+
* `countryButtonProps` is how you wire it to a `Menu`, a popover or your own
|
|
45
|
+
* listbox — including the `aria-haspopup` and `aria-expanded` that only the
|
|
46
|
+
* thing owning the popup can set honestly.
|
|
47
|
+
*/
|
|
48
|
+
export const PhoneInput = forwardRef((props, forwardedRef) => {
|
|
49
|
+
const { dialCode = '+1', countryLabel = 'Select country calling code', countryButtonProps, size = 'md', type = 'tel', isDisabled: isDisabledProp, disabled, ...inputProps } = props;
|
|
50
|
+
const isDisabled = resolveDisabled(isDisabledProp, disabled);
|
|
51
|
+
const countryClassNames = [
|
|
52
|
+
'ion-phone-input__country',
|
|
53
|
+
size !== 'md' ? `ion-phone-input__country--${size}` : '',
|
|
54
|
+
countryButtonProps?.className || '',
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(' ');
|
|
58
|
+
const country = (_jsxs("button", { type: "button", "aria-label": countryLabel, ...countryButtonProps, className: countryClassNames,
|
|
59
|
+
// Read from the resolved value rather than the raw prop, so the
|
|
60
|
+
// deprecated `disabled` alias disables the trigger too. A dial-code
|
|
61
|
+
// button that stays live beside a disabled field is a real trap: it
|
|
62
|
+
// looks operable and would open a picker for a field you cannot edit.
|
|
63
|
+
disabled: isDisabled || countryButtonProps?.disabled, children: [_jsx("span", { className: "ion-phone-input__dial-code", children: dialCode }), _jsx("span", { className: "ion-phone-input__chevron", children: _jsx(ChevronDown, {}) })] }));
|
|
64
|
+
return (_jsx(Input, { ...inputProps, ref: forwardedRef, size: size, type: type, isDisabled: isDisabled, leadingAddon: country }));
|
|
65
|
+
});
|
|
66
|
+
PhoneInput.displayName = 'PhoneInput';
|
|
67
|
+
//# sourceMappingURL=PhoneInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PhoneInput.js","sourceRoot":"","sources":["../../src/components/PhoneInput.tsx"],"names":[],"mappings":";AAAA;;;;;;GAMG;AACH,OAAc,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,CACxB,cAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,YACvE,eACE,CAAC,EAAC,cAAc,EAChB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,GACtB,GACE,CACP,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAClC,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE;IACtB,MAAM,EACJ,QAAQ,GAAG,IAAI,EACf,YAAY,GAAG,6BAA6B,EAC5C,kBAAkB,EAClB,IAAI,GAAG,IAAI,EACX,IAAI,GAAG,KAAK,EACZ,UAAU,EAAE,cAAc,EAC1B,QAAQ,EACR,GAAG,UAAU,EACd,GAAG,KAAK,CAAC;IAEV,MAAM,UAAU,GAAG,eAAe,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAE7D,MAAM,iBAAiB,GAAG;QACxB,0BAA0B;QAC1B,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;QACxD,kBAAkB,EAAE,SAAS,IAAI,EAAE;KACpC;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,MAAM,OAAO,GAAG,CACd,kBACE,IAAI,EAAC,QAAQ,gBACD,YAAY,KACpB,kBAAkB,EACtB,SAAS,EAAE,iBAAiB;QAC5B,gEAAgE;QAChE,oEAAoE;QACpE,oEAAoE;QACpE,sEAAsE;QACtE,QAAQ,EAAE,UAAU,IAAI,kBAAkB,EAAE,QAAQ,aAEpD,eAAM,SAAS,EAAC,4BAA4B,YAAE,QAAQ,GAAQ,EAC9D,eAAM,SAAS,EAAC,0BAA0B,YACxC,KAAC,WAAW,KAAG,GACV,IACA,CACV,CAAC;IAEF,OAAO,CACL,KAAC,KAAK,OACA,UAAU,EACd,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,OAAO,GACrB,CACH,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,UAAU,CAAC,WAAW,GAAG,YAAY,CAAC"}
|
|
@@ -6,6 +6,8 @@ export { Badge } from './Badge.js';
|
|
|
6
6
|
export type { BadgeProps, BadgeIntent } from './Badge.js';
|
|
7
7
|
export { Input } from './Input.js';
|
|
8
8
|
export type { InputProps, InputSize } from './Input.js';
|
|
9
|
+
export { PhoneInput } from './PhoneInput.js';
|
|
10
|
+
export type { PhoneInputProps } from './PhoneInput.js';
|
|
9
11
|
export { Select } from './Select.js';
|
|
10
12
|
export type { SelectProps, SelectSize, SelectOption } from './Select.js';
|
|
11
13
|
export { Checkbox } from './Checkbox.js';
|
|
@@ -24,6 +26,8 @@ export { Logo, LogoMark } from './Logo.js';
|
|
|
24
26
|
export type { LogoProps, LogoMarkProps, LogoSize, LogoWordmark, } from './Logo.js';
|
|
25
27
|
export { Divider } from './Divider.js';
|
|
26
28
|
export type { DividerProps, DividerOrientation } from './Divider.js';
|
|
29
|
+
export { Link } from './Link.js';
|
|
30
|
+
export type { LinkProps, LinkVariant } from './Link.js';
|
|
27
31
|
export { NavItem } from './NavItem.js';
|
|
28
32
|
export type { NavItemProps } from './NavItem.js';
|
|
29
33
|
export { Table, TableHead, TableBody, TableRow, TableCell } from './Table.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,UAAU,EACV,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,SAAS,EACT,aAAa,EACb,QAAQ,EACR,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EACV,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,UAAU,EACV,eAAe,EACf,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,SAAS,EACT,aAAa,EACb,QAAQ,EACR,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EACV,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { Button } from './Button.js';
|
|
|
2
2
|
export { Tabs, TabItem } from './Tabs.js';
|
|
3
3
|
export { Badge } from './Badge.js';
|
|
4
4
|
export { Input } from './Input.js';
|
|
5
|
+
export { PhoneInput } from './PhoneInput.js';
|
|
5
6
|
export { Select } from './Select.js';
|
|
6
7
|
export { Checkbox } from './Checkbox.js';
|
|
7
8
|
export { Radio, RadioGroup } from './Radio.js';
|
|
@@ -11,6 +12,7 @@ export { Avatar, AvatarGroup } from './Avatar.js';
|
|
|
11
12
|
export { Header } from './Header.js';
|
|
12
13
|
export { Logo, LogoMark } from './Logo.js';
|
|
13
14
|
export { Divider } from './Divider.js';
|
|
15
|
+
export { Link } from './Link.js';
|
|
14
16
|
export { NavItem } from './NavItem.js';
|
|
15
17
|
export { Table, TableHead, TableBody, TableRow, TableCell } from './Table.js';
|
|
16
18
|
export { ScrollProgress } from './ScrollProgress.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAMzC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU9E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAMzC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOlD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU9E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/styles/index.css
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
@import url('./tabs.css');
|
|
32
32
|
@import url('./badge.css');
|
|
33
33
|
@import url('./input.css');
|
|
34
|
+
@import url('./phone-input.css');
|
|
34
35
|
@import url('./select.css');
|
|
35
36
|
@import url('./checkbox.css');
|
|
36
37
|
@import url('./radio.css');
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
@import url('./logo.css');
|
|
42
43
|
@import url('./table.css');
|
|
43
44
|
@import url('./divider.css');
|
|
45
|
+
@import url('./link.css');
|
|
44
46
|
@import url('./nav-item.css');
|
|
45
47
|
@import url('./scroll-progress.css');
|
|
46
48
|
@import url('./full-card.css');
|
package/dist/styles/input.css
CHANGED
|
@@ -89,6 +89,25 @@
|
|
|
89
89
|
--ion-input-padding-x: var(--spacing-16);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/*
|
|
93
|
+
* A leading addon and the control, sharing one outline — Figma `Input/Phone`
|
|
94
|
+
* (80:372).
|
|
95
|
+
*
|
|
96
|
+
* `align-items: stretch` rather than a height here on purpose: the addon takes
|
|
97
|
+
* its height from the control, so the size ladder stays in exactly one place
|
|
98
|
+
* and the two halves cannot drift. The addon supplies its own left radii; this
|
|
99
|
+
* rule only has to square off the edge they meet on.
|
|
100
|
+
*/
|
|
101
|
+
.ion-input-group {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: stretch;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ion-input-group > .ion-input {
|
|
107
|
+
border-start-start-radius: 0;
|
|
108
|
+
border-end-start-radius: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
/*
|
|
93
112
|
* The field itself carries no box of its own — the wrapper owns the border,
|
|
94
113
|
* background and padding so the icons sit inside it. Everything here is either
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Link
|
|
3
|
+
*
|
|
4
|
+
* Measured from Figma `Link` (774:1516). Two types x five states.
|
|
5
|
+
*
|
|
6
|
+
* NO SIZE LADDER, AND THAT IS THE POINT
|
|
7
|
+
*
|
|
8
|
+
* Every other control here pins its own type ramp because it owns a box. A
|
|
9
|
+
* link owns nothing: it sits inside a sentence and has to be the size of that
|
|
10
|
+
* sentence. So `font-size` and `line-height` inherit, and the icons are sized
|
|
11
|
+
* in `em` rather than from the `icon-size` ladder — a 14px link gets a 14px
|
|
12
|
+
* icon without being told.
|
|
13
|
+
*
|
|
14
|
+
* Figma draws its variants at 16/24 (`type/body`) because a Figma variant must
|
|
15
|
+
* have a concrete size. That is one sample of an inheriting element, not a
|
|
16
|
+
* specification, and the drawing should not be used to justify adding a size
|
|
17
|
+
* prop here.
|
|
18
|
+
*/
|
|
19
|
+
.ion-link {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--spacing-4);
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* `baseline` rather than the flex default, so a link with an icon still sits
|
|
26
|
+
* on the same baseline as the prose around it. `inline-flex` alone would
|
|
27
|
+
* align the whole box to the text baseline and push the label off it.
|
|
28
|
+
*/
|
|
29
|
+
vertical-align: baseline;
|
|
30
|
+
color: var(--text-link);
|
|
31
|
+
font-family: inherit;
|
|
32
|
+
font-size: inherit;
|
|
33
|
+
line-height: inherit;
|
|
34
|
+
font-weight: inherit;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
|
|
37
|
+
/* Reset for the <button> form — it is a link, not a control with a box. */
|
|
38
|
+
padding: 0;
|
|
39
|
+
background: none;
|
|
40
|
+
border: none;
|
|
41
|
+
transition: color var(--ion-duration-base) var(--ion-ease-out);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Inline is underlined in EVERY state.
|
|
46
|
+
*
|
|
47
|
+
* This is the accessibility half of the Type axis, not a stylistic preference:
|
|
48
|
+
* a link inside a paragraph that is distinguished only by colour fails WCAG
|
|
49
|
+
* 1.4.1. `text-underline-offset` keeps the rule off the descenders, which is
|
|
50
|
+
* what makes an always-on underline tolerable in body copy.
|
|
51
|
+
*/
|
|
52
|
+
.ion-link--inline {
|
|
53
|
+
text-decoration: underline;
|
|
54
|
+
text-underline-offset: 0.2em;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* Standalone earns its lack of underline from context — a table cell, a list,
|
|
59
|
+
* a card footer — where position already says "interactive". It still
|
|
60
|
+
* underlines on hover, so the affordance is never absent, only deferred.
|
|
61
|
+
*/
|
|
62
|
+
.ion-link--standalone {
|
|
63
|
+
text-decoration: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* Hover is declared twice, as it is on Button and Input. Native `:hover`
|
|
68
|
+
* latches on touch until you tap elsewhere; `[data-hovered]` comes from React
|
|
69
|
+
* Aria's useHover, which is pointer-aware. The media query keeps the
|
|
70
|
+
* stylesheet working without React.
|
|
71
|
+
*/
|
|
72
|
+
@media (hover: hover) {
|
|
73
|
+
.ion-link:hover {
|
|
74
|
+
color: var(--text-link-hover);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ion-link--standalone:hover {
|
|
78
|
+
text-decoration: underline;
|
|
79
|
+
text-underline-offset: 0.2em;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ion-link[data-hovered='true'] {
|
|
84
|
+
color: var(--text-link-hover);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ion-link--standalone[data-hovered='true'] {
|
|
88
|
+
text-decoration: underline;
|
|
89
|
+
text-underline-offset: 0.2em;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
* Visited is colour-only, and it has to be.
|
|
94
|
+
*
|
|
95
|
+
* Browsers deliberately restrict `:visited` to a short list of properties to
|
|
96
|
+
* close a history-sniffing side channel — `getComputedStyle` lies about it,
|
|
97
|
+
* and layout-affecting properties are ignored outright. Colour is on the
|
|
98
|
+
* allowed list, which is exactly why `text/link/visited` is a colour and why
|
|
99
|
+
* this rule tries nothing else.
|
|
100
|
+
*
|
|
101
|
+
* Only anchors have history, so this is scoped to `a`. The `<button>` form has
|
|
102
|
+
* no visited state to express.
|
|
103
|
+
*/
|
|
104
|
+
a.ion-link:visited {
|
|
105
|
+
color: var(--text-link-visited);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Hover after visited, so a visited link still responds to the pointer. */
|
|
109
|
+
@media (hover: hover) {
|
|
110
|
+
a.ion-link:visited:hover {
|
|
111
|
+
color: var(--text-link-hover);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
a.ion-link:visited[data-hovered='true'] {
|
|
116
|
+
color: var(--text-link-hover);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* Focus is a ring, not the `Focus/md` effect style every boxed control uses.
|
|
121
|
+
*
|
|
122
|
+
* That style is a drop shadow, which needs a filled box to cast from — a link
|
|
123
|
+
* has no surface, so it rendered as a faint glow off the glyph shapes instead
|
|
124
|
+
* of a ring. `ring/focus` is the token for this and had no consumer anywhere
|
|
125
|
+
* until this component.
|
|
126
|
+
*
|
|
127
|
+
* `outline-offset` is what Figma cannot express: there, the ring hugs the text
|
|
128
|
+
* because adding padding would change the box and knock an inline link out of
|
|
129
|
+
* alignment with its sentence. An outline is drawn outside the box and costs
|
|
130
|
+
* no layout, so the breathing room is free here.
|
|
131
|
+
*/
|
|
132
|
+
.ion-link:focus-visible,
|
|
133
|
+
.ion-link[data-focused='true'] {
|
|
134
|
+
outline: var(--border-width-thick) solid var(--ring-focus);
|
|
135
|
+
outline-offset: 2px;
|
|
136
|
+
border-radius: var(--radius-xs);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ion-link--disabled,
|
|
140
|
+
.ion-link:disabled,
|
|
141
|
+
.ion-link[data-disabled='true'] {
|
|
142
|
+
color: var(--text-disabled);
|
|
143
|
+
cursor: not-allowed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* Icons take the label's colour rather than an `icon/*` role.
|
|
148
|
+
*
|
|
149
|
+
* There is deliberately no `color` declaration here, and its absence is the
|
|
150
|
+
* mechanism rather than an omission. Colour inherits, so the icon picks up
|
|
151
|
+
* every state — hover, visited, disabled — without a rule of its own and
|
|
152
|
+
* cannot drift from the text it belongs to. A `color: currentColor` line would
|
|
153
|
+
* restate the default while reading as though something were being decided.
|
|
154
|
+
*
|
|
155
|
+
* This is deliberately not the usual v2 split between `text/*` and `icon/*`:
|
|
156
|
+
* the icon makes no colour decision at all. Figma binds the label's token onto
|
|
157
|
+
* the icon vectors to depict the same relationship, because Figma has no
|
|
158
|
+
* inheritance — which is why the icons sat at `icon/default` (near-black) on a
|
|
159
|
+
* blue link until it was caught in review.
|
|
160
|
+
*/
|
|
161
|
+
.ion-link__icon-start,
|
|
162
|
+
.ion-link__icon-end {
|
|
163
|
+
display: inline-flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
justify-content: center;
|
|
166
|
+
flex-shrink: 0;
|
|
167
|
+
|
|
168
|
+
/* The underline belongs to the text, never to the icons. */
|
|
169
|
+
text-decoration: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/*
|
|
173
|
+
* `em`, not the `icon-size` ladder — the icon scales with whatever type the
|
|
174
|
+
* link inherited. `1em` against a 16px label is 16px, which is `icon-size/sm`,
|
|
175
|
+
* the rung Figma draws.
|
|
176
|
+
*/
|
|
177
|
+
.ion-link__icon-start svg,
|
|
178
|
+
.ion-link__icon-end svg {
|
|
179
|
+
width: 1em;
|
|
180
|
+
height: 1em;
|
|
181
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Phone Input
|
|
3
|
+
*
|
|
4
|
+
* Measured from Figma `Input/Phone` (80:372). Three sizes, no State axis —
|
|
5
|
+
* every state belongs to the Input it wraps.
|
|
6
|
+
*
|
|
7
|
+
* THE SEAM IS TWO BORDERS, NOT ONE
|
|
8
|
+
*
|
|
9
|
+
* Figma butts the two frames at x = 68 (Medium) with no gap, and both keep a
|
|
10
|
+
* full 1px stroke on all four sides. So the divider is 2px of `border/default`,
|
|
11
|
+
* and reproducing it needs no negative margin and no shared-edge trick — plain
|
|
12
|
+
* adjacency gives exactly the measured result. The corners facing the seam are
|
|
13
|
+
* square on both sides, which is what makes the pair read as one control:
|
|
14
|
+
* `Country Code` is [r,0,0,r] and the Input is [0,r,r,0], both bound.
|
|
15
|
+
*
|
|
16
|
+
* Radius tracks the size the same way the Input's does — `radius/sm` at Small,
|
|
17
|
+
* `radius/md` at Medium and Large — so the two halves round in step. Getting
|
|
18
|
+
* this wrong is invisible until Small, where an 8px block meets a 6px field.
|
|
19
|
+
*/
|
|
20
|
+
.ion-phone-input__country {
|
|
21
|
+
/*
|
|
22
|
+
* Slots, defaulting to Medium, same shape as `.ion-input`. Padding, icon and
|
|
23
|
+
* type deliberately match the Input's rungs at every size, because Figma
|
|
24
|
+
* composes this from that component and a divergence here would show as the
|
|
25
|
+
* dial code sitting off the value's baseline.
|
|
26
|
+
*
|
|
27
|
+
* GAP IS THE ONE THAT DOES NOT MATCH: 4 at every size, where the Input runs
|
|
28
|
+
* 6/8/8. Measured, not inferred — the block holds two tightly-related glyphs
|
|
29
|
+
* rather than an icon and a sentence.
|
|
30
|
+
*/
|
|
31
|
+
--ion-phone-input-padding-x: var(--spacing-12);
|
|
32
|
+
--ion-phone-input-gap: var(--spacing-4);
|
|
33
|
+
--ion-phone-input-icon-size: var(--icon-size-md);
|
|
34
|
+
--ion-phone-input-radius: var(--radius-md);
|
|
35
|
+
--ion-phone-input-font-size: var(--type-body);
|
|
36
|
+
--ion-phone-input-line-height: var(--type-body-line-height);
|
|
37
|
+
--ion-phone-input-border-width: var(--border-width-default);
|
|
38
|
+
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
gap: var(--ion-phone-input-gap);
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* No height of its own: the row stretches it to the control beside it, so the
|
|
47
|
+
* two halves cannot drift apart. Pinning a height here would duplicate the
|
|
48
|
+
* Input's size ladder and then disagree with it the first time one moved.
|
|
49
|
+
*/
|
|
50
|
+
padding-block: 0;
|
|
51
|
+
|
|
52
|
+
/* Figma's stroke is inside the frame; a CSS border is outside the padding
|
|
53
|
+
* box. Subtracting it puts the dial code where the design has it — the same
|
|
54
|
+
* correction `.ion-input` documents at length. */
|
|
55
|
+
padding-inline: calc(
|
|
56
|
+
var(--ion-phone-input-padding-x) - var(--ion-phone-input-border-width)
|
|
57
|
+
);
|
|
58
|
+
background-color: var(--surface-page);
|
|
59
|
+
border-style: solid;
|
|
60
|
+
border-width: var(--ion-phone-input-border-width);
|
|
61
|
+
border-color: var(--border-default);
|
|
62
|
+
border-start-start-radius: var(--ion-phone-input-radius);
|
|
63
|
+
border-end-start-radius: var(--ion-phone-input-radius);
|
|
64
|
+
border-start-end-radius: 0;
|
|
65
|
+
border-end-end-radius: 0;
|
|
66
|
+
color: var(--text-secondary);
|
|
67
|
+
font-family: var(--font-family-sans);
|
|
68
|
+
font-size: var(--ion-phone-input-font-size);
|
|
69
|
+
line-height: var(--ion-phone-input-line-height);
|
|
70
|
+
font-weight: var(--font-weight-regular);
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
transition:
|
|
73
|
+
border-color var(--ion-duration-base) var(--ion-ease-out),
|
|
74
|
+
background-color var(--ion-duration-base) var(--ion-ease-out);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ion-phone-input__country--sm {
|
|
78
|
+
--ion-phone-input-icon-size: var(--icon-size-sm);
|
|
79
|
+
--ion-phone-input-radius: var(--radius-sm);
|
|
80
|
+
--ion-phone-input-font-size: var(--type-body-sm);
|
|
81
|
+
--ion-phone-input-line-height: var(--type-body-sm-line-height);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Large grows its padding only — icon, radius and type stay at Medium's rung,
|
|
85
|
+
* exactly as `.ion-input--lg` does. */
|
|
86
|
+
.ion-phone-input__country--lg {
|
|
87
|
+
--ion-phone-input-padding-x: var(--spacing-16);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ion-phone-input__chevron {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
color: var(--icon-tertiary);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ion-phone-input__chevron svg {
|
|
99
|
+
width: var(--ion-phone-input-icon-size);
|
|
100
|
+
height: var(--ion-phone-input-icon-size);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (hover: hover) {
|
|
104
|
+
.ion-phone-input__country:hover:not(:disabled) {
|
|
105
|
+
border-color: var(--border-strong);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* Raised above the control while focused so the ring is not clipped by the
|
|
111
|
+
* neighbour's border. Inset offset because Figma draws focus as an inside
|
|
112
|
+
* stroke — matching `.ion-input`, which thickens its border rather than
|
|
113
|
+
* painting an outline outside the box.
|
|
114
|
+
*/
|
|
115
|
+
.ion-phone-input__country:focus-visible {
|
|
116
|
+
outline: var(--border-width-thick) solid var(--border-focus);
|
|
117
|
+
outline-offset: calc(-1 * var(--border-width-thick));
|
|
118
|
+
position: relative;
|
|
119
|
+
z-index: 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.ion-phone-input__country:disabled {
|
|
123
|
+
background-color: var(--surface-disabled);
|
|
124
|
+
border-color: var(--border-disabled);
|
|
125
|
+
color: var(--text-disabled);
|
|
126
|
+
cursor: not-allowed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.ion-phone-input__country:disabled .ion-phone-input__chevron {
|
|
130
|
+
color: var(--icon-disabled);
|
|
131
|
+
}
|