@kanso-labs/react-native-kanso-ui 0.0.0-stage → 0.1.1
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/LICENSE.md +20 -0
- package/README.md +205 -2
- package/dist/button/index.d.ts +56 -0
- package/dist/button/index.js +169 -0
- package/dist/button/index.js.map +1 -0
- package/dist/button/palettes.js +47 -0
- package/dist/button/palettes.js.map +1 -0
- package/dist/chip/index.js +194 -0
- package/dist/chip/index.js.map +1 -0
- package/dist/chip/styles.js +36 -0
- package/dist/chip/styles.js.map +1 -0
- package/dist/clipboard/index.js +58 -0
- package/dist/clipboard/index.js.map +1 -0
- package/dist/components/avatar/index.d.ts +82 -0
- package/dist/components/avatar/index.js +62 -0
- package/dist/components/avatar/index.js.map +1 -0
- package/dist/components/button/index.d.ts +87 -0
- package/dist/components/button/index.js +377 -0
- package/dist/components/button/index.js.map +1 -0
- package/dist/components/card/index.d.ts +78 -0
- package/dist/components/card/index.js +315 -0
- package/dist/components/card/index.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +76 -0
- package/dist/components/checkbox/index.js +360 -0
- package/dist/components/checkbox/index.js.map +1 -0
- package/dist/components/chip/index.d.ts +84 -0
- package/dist/components/chip/index.js +257 -0
- package/dist/components/chip/index.js.map +1 -0
- package/dist/components/chip-group/index.d.ts +143 -0
- package/dist/components/chip-group/index.js +669 -0
- package/dist/components/chip-group/index.js.map +1 -0
- package/dist/components/copy-field/index.d.ts +60 -0
- package/dist/components/copy-field/index.js +277 -0
- package/dist/components/copy-field/index.js.map +1 -0
- package/dist/components/currency/index.d.ts +69 -0
- package/dist/components/currency/index.js +162 -0
- package/dist/components/currency/index.js.map +1 -0
- package/dist/components/form/index.d.ts +84 -0
- package/dist/components/form/index.js +95 -0
- package/dist/components/form/index.js.map +1 -0
- package/dist/components/icon-button/index.d.ts +68 -0
- package/dist/components/icon-button/index.js +289 -0
- package/dist/components/icon-button/index.js.map +1 -0
- package/dist/components/link/index.d.ts +59 -0
- package/dist/components/link/index.js +191 -0
- package/dist/components/link/index.js.map +1 -0
- package/dist/components/number-field/index.d.ts +151 -0
- package/dist/components/number-field/index.js +832 -0
- package/dist/components/number-field/index.js.map +1 -0
- package/dist/components/product-icon/index.d.ts +70 -0
- package/dist/components/product-icon/index.js +60 -0
- package/dist/components/product-icon/index.js.map +1 -0
- package/dist/components/radio-group/index.d.ts +105 -0
- package/dist/components/radio-group/index.js +597 -0
- package/dist/components/radio-group/index.js.map +1 -0
- package/dist/components/search-field/index.d.ts +98 -0
- package/dist/components/search-field/index.js +521 -0
- package/dist/components/search-field/index.js.map +1 -0
- package/dist/components/segmented-button/index.d.ts +113 -0
- package/dist/components/segmented-button/index.js +937 -0
- package/dist/components/segmented-button/index.js.map +1 -0
- package/dist/components/separator/index.d.ts +45 -0
- package/dist/components/separator/index.js +109 -0
- package/dist/components/separator/index.js.map +1 -0
- package/dist/components/stack/index.d.ts +70 -0
- package/dist/components/stack/index.js +107 -0
- package/dist/components/stack/index.js.map +1 -0
- package/dist/components/switch/index.d.ts +76 -0
- package/dist/components/switch/index.js +591 -0
- package/dist/components/switch/index.js.map +1 -0
- package/dist/components/text/index.d.ts +44 -0
- package/dist/components/text/index.js +78 -0
- package/dist/components/text/index.js.map +1 -0
- package/dist/components/text-field/index.d.ts +146 -0
- package/dist/components/text-field/index.js +228 -0
- package/dist/components/text-field/index.js.map +1 -0
- package/dist/control/index.js +117 -0
- package/dist/control/index.js.map +1 -0
- package/dist/control/styles.js +36 -0
- package/dist/control/styles.js.map +1 -0
- package/dist/field/index.d.ts +13 -0
- package/dist/field/index.js +1007 -0
- package/dist/field/index.js.map +1 -0
- package/dist/field/styles.js +131 -0
- package/dist/field/styles.js.map +1 -0
- package/dist/form/index.d.ts +24 -0
- package/dist/form/index.js +209 -0
- package/dist/form/index.js.map +1 -0
- package/dist/glyphs/icon-style.d.ts +25 -0
- package/dist/glyphs/icon-style.js +22 -0
- package/dist/glyphs/icon-style.js.map +1 -0
- package/dist/glyphs/index.js +170 -0
- package/dist/glyphs/index.js.map +1 -0
- package/dist/glyphs/styles.js +7 -0
- package/dist/glyphs/styles.js.map +1 -0
- package/dist/hooks/useWindowSizeClass.d.ts +25 -0
- package/dist/hooks/useWindowSizeClass.js +39 -0
- package/dist/hooks/useWindowSizeClass.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +27 -0
- package/dist/mark/image.js +61 -0
- package/dist/mark/image.js.map +1 -0
- package/dist/mark/index.d.ts +9 -0
- package/dist/mark/index.js +234 -0
- package/dist/mark/index.js.map +1 -0
- package/dist/mark/initials.js +65 -0
- package/dist/mark/initials.js.map +1 -0
- package/dist/mark/styles.d.ts +1 -0
- package/dist/mark/styles.js +73 -0
- package/dist/mark/styles.js.map +1 -0
- package/dist/number/parser.js +321 -0
- package/dist/number/parser.js.map +1 -0
- package/dist/number/state.js +235 -0
- package/dist/number/state.js.map +1 -0
- package/dist/overlay/host.js +111 -0
- package/dist/overlay/host.js.map +1 -0
- package/dist/press/index.js +350 -0
- package/dist/press/index.js.map +1 -0
- package/dist/press/styles.js +31 -0
- package/dist/press/styles.js.map +1 -0
- package/dist/router/index.d.ts +28 -0
- package/dist/router/index.js +82 -0
- package/dist/router/index.js.map +1 -0
- package/dist/theme/color.js +25 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/resolve.d.ts +6 -0
- package/dist/theme/resolve.js +310 -0
- package/dist/theme/resolve.js.map +1 -0
- package/dist/theme/scope.d.ts +42 -0
- package/dist/theme/scope.js +80 -0
- package/dist/theme/scope.js.map +1 -0
- package/dist/theme/styles.js +39 -0
- package/dist/theme/styles.js.map +1 -0
- package/dist/theme-scope.d.ts +56 -0
- package/dist/theme-scope.js +127 -0
- package/dist/theme-scope.js.map +1 -0
- package/dist/tokens/defaults.d.ts +407 -0
- package/dist/tokens/defaults.js +403 -0
- package/dist/tokens/defaults.js.map +1 -0
- package/dist/typography/fonts.js +84 -0
- package/dist/typography/fonts.js.map +1 -0
- package/dist/typography/roles.d.ts +10 -0
- package/dist/typography/roles.js +82 -0
- package/dist/typography/roles.js.map +1 -0
- package/package.json +132 -4
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useTheme } from "../../theme/scope.js";
|
|
2
|
+
import { composeStyles, themedStyles } from "../../theme/styles.js";
|
|
3
|
+
import { useFontFamily } from "../../typography/fonts.js";
|
|
4
|
+
import { familyStyle, nativeWeight } from "../../typography/roles.js";
|
|
5
|
+
import { c } from "react/compiler-runtime";
|
|
6
|
+
import { StyleSheet, Text } from "react-native";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/currency/index.tsx
|
|
9
|
+
const stylesOf = themedStyles(({ colors, typography }) => StyleSheet.create({
|
|
10
|
+
base: {
|
|
11
|
+
fontVariant: ["tabular-nums"],
|
|
12
|
+
fontWeight: nativeWeight(typography.weightMedium)
|
|
13
|
+
},
|
|
14
|
+
negative: { color: colors.negative },
|
|
15
|
+
neutral: { color: colors.onSurface },
|
|
16
|
+
positive: { color: colors.positive }
|
|
17
|
+
}));
|
|
18
|
+
const MINUS_SIGN = "−";
|
|
19
|
+
const PLUS_SIGN = "+";
|
|
20
|
+
const LOCALE_MINUS = /[-\u2212]/u;
|
|
21
|
+
/**
|
|
22
|
+
* An amount of money, formatted for the reader's locale in the mono face with
|
|
23
|
+
* tabular figures, so a column of amounts lines up on the decimal point.
|
|
24
|
+
* Negatives carry a true minus sign, and the tone follows the sign unless
|
|
25
|
+
* `tone` names one.
|
|
26
|
+
*
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <Text variant="bodyLarge">
|
|
29
|
+
* <Currency currency="EUR" value={-42.5} />
|
|
30
|
+
* </Text>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* It sets no size of its own, so it takes the type of the Text around it:
|
|
34
|
+
* put it in one, a heading-scale one for a larger figure. On its own it draws
|
|
35
|
+
* at the platform's default size. The face is the mono family's first, once
|
|
36
|
+
* the app has registered it with expo-font, and the system font until then,
|
|
37
|
+
* with tabular figures either way.
|
|
38
|
+
*
|
|
39
|
+
* Web's `render` has no counterpart. `style`, `ref` and `testID` land on React
|
|
40
|
+
* Native's own Text.
|
|
41
|
+
*/
|
|
42
|
+
function Currency(t0) {
|
|
43
|
+
const $ = c(13);
|
|
44
|
+
let locale;
|
|
45
|
+
let props;
|
|
46
|
+
let style;
|
|
47
|
+
let t1;
|
|
48
|
+
let t2;
|
|
49
|
+
let t3;
|
|
50
|
+
let value;
|
|
51
|
+
if ($[0] !== t0) {
|
|
52
|
+
({currency: t1, locale, sign: t2, style, tone: t3, value, ...props} = t0);
|
|
53
|
+
$[0] = t0;
|
|
54
|
+
$[1] = locale;
|
|
55
|
+
$[2] = props;
|
|
56
|
+
$[3] = style;
|
|
57
|
+
$[4] = t1;
|
|
58
|
+
$[5] = t2;
|
|
59
|
+
$[6] = t3;
|
|
60
|
+
$[7] = value;
|
|
61
|
+
} else {
|
|
62
|
+
locale = $[1];
|
|
63
|
+
props = $[2];
|
|
64
|
+
style = $[3];
|
|
65
|
+
t1 = $[4];
|
|
66
|
+
t2 = $[5];
|
|
67
|
+
t3 = $[6];
|
|
68
|
+
value = $[7];
|
|
69
|
+
}
|
|
70
|
+
const currency = t1 === void 0 ? "USD" : t1;
|
|
71
|
+
const sign = t2 === void 0 ? "auto" : t2;
|
|
72
|
+
const tone = t3 === void 0 ? "auto" : t3;
|
|
73
|
+
const theme = useTheme();
|
|
74
|
+
const own = stylesOf(theme);
|
|
75
|
+
const family = familyStyle(useFontFamily(theme.typography.fontFamilyMono));
|
|
76
|
+
const amount = formatCurrency(value, {
|
|
77
|
+
currency,
|
|
78
|
+
locale,
|
|
79
|
+
sign
|
|
80
|
+
});
|
|
81
|
+
const T0 = Text;
|
|
82
|
+
const t4 = composeStyles(own.base, family, own[resolveTone(value, tone, amount.shownAsZero)], style);
|
|
83
|
+
let t5;
|
|
84
|
+
if ($[8] !== T0 || $[9] !== amount.text || $[10] !== props || $[11] !== t4) {
|
|
85
|
+
t5 = /* @__PURE__ */ jsx(T0, {
|
|
86
|
+
...props,
|
|
87
|
+
style: t4,
|
|
88
|
+
children: amount.text
|
|
89
|
+
});
|
|
90
|
+
$[8] = T0;
|
|
91
|
+
$[9] = amount.text;
|
|
92
|
+
$[10] = props;
|
|
93
|
+
$[11] = t4;
|
|
94
|
+
$[12] = t5;
|
|
95
|
+
} else t5 = $[12];
|
|
96
|
+
return t5;
|
|
97
|
+
}
|
|
98
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
99
|
+
function formatCurrency(value, options) {
|
|
100
|
+
const { code, formatter, zero } = formatterFor(options.locale, options.currency);
|
|
101
|
+
const magnitude = formatter.format(Math.abs(value));
|
|
102
|
+
const shownAsZero = magnitude === zero;
|
|
103
|
+
const mark = signFor(value, options.sign, shownAsZero);
|
|
104
|
+
const amount = mark === void 0 ? magnitude : signed(formatter, value, mark);
|
|
105
|
+
return {
|
|
106
|
+
shownAsZero,
|
|
107
|
+
text: code === null || code === "" ? amount : `${amount} ${code}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function formatterFor(locale, currency) {
|
|
111
|
+
const key = `${locale ?? ""}|${currency}`;
|
|
112
|
+
const cached = formatters.get(key);
|
|
113
|
+
if (cached !== void 0) return cached;
|
|
114
|
+
const usable = locale !== void 0 && isLocale(locale) ? locale : void 0;
|
|
115
|
+
let formatter;
|
|
116
|
+
let code = null;
|
|
117
|
+
try {
|
|
118
|
+
formatter = new Intl.NumberFormat(usable, {
|
|
119
|
+
currency,
|
|
120
|
+
style: "currency"
|
|
121
|
+
});
|
|
122
|
+
} catch {
|
|
123
|
+
formatter = new Intl.NumberFormat(usable, {
|
|
124
|
+
maximumFractionDigits: 2,
|
|
125
|
+
minimumFractionDigits: 2
|
|
126
|
+
});
|
|
127
|
+
code = currency;
|
|
128
|
+
}
|
|
129
|
+
const resolved = {
|
|
130
|
+
code,
|
|
131
|
+
formatter,
|
|
132
|
+
zero: formatter.format(0)
|
|
133
|
+
};
|
|
134
|
+
formatters.set(key, resolved);
|
|
135
|
+
return resolved;
|
|
136
|
+
}
|
|
137
|
+
function isLocale(tag) {
|
|
138
|
+
try {
|
|
139
|
+
return Intl.NumberFormat.supportedLocalesOf(tag).length >= 0;
|
|
140
|
+
} catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function resolveTone(value, tone, shownAsZero) {
|
|
145
|
+
if (tone !== "auto") return tone;
|
|
146
|
+
if (shownAsZero) return "neutral";
|
|
147
|
+
if (value > 0) return "positive";
|
|
148
|
+
if (value < 0) return "negative";
|
|
149
|
+
return "neutral";
|
|
150
|
+
}
|
|
151
|
+
function signed(formatter, value, mark) {
|
|
152
|
+
return formatter.format(-Math.abs(value)).replace(LOCALE_MINUS, mark);
|
|
153
|
+
}
|
|
154
|
+
function signFor(value, sign, shownAsZero) {
|
|
155
|
+
if (shownAsZero || sign === "never") return;
|
|
156
|
+
if (value < 0) return MINUS_SIGN;
|
|
157
|
+
if (value > 0 && sign === "always") return PLUS_SIGN;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { Currency };
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["ComponentRef","Ref","TextProps","NativeTextProps","TextStyle","Text","NativeText","StyleSheet","useTheme","composeStyles","themedStyles","useFontFamily","familyStyle","nativeWeight","stylesOf","colors","typography","create","base","fontVariant","fontWeight","weightMedium","negative","color","neutral","onSurface","positive","MINUS_SIGN","PLUS_SIGN","LOCALE_MINUS","CurrencyProps","Omit","currency","locale","ref","sign","CurrencySignDisplay","tone","CurrencyTone","value","ResolvedFormat","code","formatter","Intl","NumberFormat","zero","Currency","t0","$","_c","props","style","t1","t2","t3","undefined","theme","own","family","fontFamilyMono","amount","formatCurrency","T0","t4","resolveTone","shownAsZero","t5","text","formatters","Map","options","formatterFor","magnitude","format","Math","abs","mark","signFor","signed","key","cached","get","usable","isLocale","maximumFractionDigits","minimumFractionDigits","resolved","set","tag","supportedLocalesOf","length","replace","_c","NativeText"],"sources":["../../../src/components/currency/index.tsx"],"sourcesContent":["import type { ComponentRef, Ref } from 'react'\nimport type { TextProps as NativeTextProps, TextStyle } from 'react-native'\n\nimport { Text as NativeText, StyleSheet } from 'react-native'\n\nimport { useTheme } from '../../theme/scope'\nimport { composeStyles, themedStyles } from '../../theme/styles'\nimport { useFontFamily } from '../../typography/fonts'\nimport { familyStyle, nativeWeight } from '../../typography/roles'\n\n// Deliberately sets no font size or line height, as web's sets none. The same\n// amount turns up at several sizes, inside a list row, as a heading-scale\n// figure, as a total, differing in nothing else, so the size is the decision\n// of the Text around it. What must never vary is here: the mono face, tabular\n// figures so a column of them lines up on the decimal point, and the medium\n// weight that separates a figure from the prose around it.\n//\n// No Material Design page draws an amount. It is type alone: the mono face\n// and medium weight from the type tokens, tabular figures, and the on surface\n// role for the neutral tone. Positive and negative are the library's own\n// roles.\nconst stylesOf = themedStyles(({ colors, typography }) =>\n StyleSheet.create({\n base: {\n fontVariant: ['tabular-nums'],\n fontWeight: nativeWeight(typography.weightMedium),\n },\n negative: {\n color: colors.negative,\n },\n neutral: {\n color: colors.onSurface,\n },\n positive: {\n color: colors.positive,\n },\n }),\n)\n\n// U+2212 MINUS SIGN, not the U+002D hyphen Intl actually emits. The hyphen is\n// a typographic stand-in: it is narrower than the plus it pairs with and does\n// not sit on the same optical centre as the digits, which shows up as a\n// wobbling column the moment amounts are stacked.\nconst MINUS_SIGN = '\\u2212'\n\nconst PLUS_SIGN = '+'\n\n// The minus a locale writes, which is the hyphen in most and U+2212 already in\n// a few, such as Swedish and Persian.\nconst LOCALE_MINUS = /[-\\u2212]/u\n\ntype CurrencyProps = Omit<NativeTextProps, 'children'> & {\n /**\n * ISO 4217 code for the currency to format in.\n * @default 'USD'\n */\n readonly currency?: string\n /**\n * BCP 47 locale to format for. Defaults to the device's, which is the one\n * the runtime formats in when it is handed none.\n */\n readonly locale?: string\n readonly ref?: Ref<ComponentRef<typeof NativeText>>\n /**\n * When to show the sign. `auto` shows one only on negatives, `always` also\n * prefixes positives with a plus, and `never` shows neither, which leaves\n * colour as the only thing separating a credit from a debt, so reach for it\n * only where the direction is already stated some other way.\n *\n * An amount that shows as zero carries no sign under any of them, since\n * there is nothing owed or owing: `-0.004` reads `$0.00`, not `−$0.00`.\n * @default 'auto'\n */\n readonly sign?: CurrencySignDisplay\n /**\n * Which colour role to render in. `auto` follows the sign of the amount as\n * it is shown, so one that rounds to zero, float residue or a fraction of a\n * cent, reads as neutral rather than as a gain or a debt.\n *\n * The positive and negative roles are guaranteed legible on the surface\n * family, which is what the library's own tokens and every demo scheme are\n * held to. Over a container of its own, such as a filled card, the pair is\n * one colour family on top of another and is not guaranteed, so a custom\n * theme placing one there has to check that pair itself.\n * @default 'auto'\n */\n readonly tone?: CurrencyTone\n /** The amount, in the currency's major units. */\n readonly value: number\n}\n\ntype CurrencySignDisplay = 'always' | 'auto' | 'never'\n\ntype CurrencyTone = 'auto' | 'negative' | 'neutral' | 'positive'\n\n/**\n * A formatter, how it writes zero, and the currency code written beside its\n * number when it is the decimal fallback for a code the runtime rejected.\n */\ntype ResolvedFormat = {\n readonly code: null | string\n readonly formatter: Intl.NumberFormat\n readonly zero: string\n}\n\n/**\n * An amount of money, formatted for the reader's locale in the mono face with\n * tabular figures, so a column of amounts lines up on the decimal point.\n * Negatives carry a true minus sign, and the tone follows the sign unless\n * `tone` names one.\n *\n * ```tsx\n * <Text variant=\"bodyLarge\">\n * <Currency currency=\"EUR\" value={-42.5} />\n * </Text>\n * ```\n *\n * It sets no size of its own, so it takes the type of the Text around it:\n * put it in one, a heading-scale one for a larger figure. On its own it draws\n * at the platform's default size. The face is the mono family's first, once\n * the app has registered it with expo-font, and the system font until then,\n * with tabular figures either way.\n *\n * Web's `render` has no counterpart. `style`, `ref` and `testID` land on React\n * Native's own Text.\n */\nfunction Currency({\n currency = 'USD',\n locale,\n sign = 'auto',\n style,\n tone = 'auto',\n value,\n ...props\n}: CurrencyProps) {\n const theme = useTheme()\n const own = stylesOf(theme)\n const family = familyStyle(useFontFamily(theme.typography.fontFamilyMono))\n\n const amount = formatCurrency(value, { currency, locale, sign })\n\n return (\n <NativeText\n {...props}\n style={composeStyles<TextStyle>(\n own.base,\n family,\n own[resolveTone(value, tone, amount.shownAsZero)],\n style,\n )}\n >\n {amount.text}\n </NativeText>\n )\n}\n\n// Constructing an Intl.NumberFormat resolves the locale, loads the currency's\n// data and builds the pattern; formatting with one already built skips all\n// three. An instance is stateless once constructed, so one can be shared\n// across every component asking for the same locale and currency. The sign\n// is no part of the key, since every setting is drawn from the one formatter.\n//\n// What this saves is a mount and a changing amount, not an idle re-render:\n// the React Compiler already memoises the call below on the things it reads,\n// so a component re-rendered with the same props never reaches here. That\n// cache belongs to one component instance and counts `value` among its keys,\n// which leaves two cases it cannot cover: a column of rows, where every\n// instance has a cache of its own and each builds a formatter, and a single\n// amount that ticks, where each new value misses.\n//\n// Unbounded on purpose. The keys are the pairs an app actually uses, its\n// locales times its currencies, so the map settles at that size rather than\n// growing with renders or with values.\n//\n// A request the runtime rejects is cached too, under its own key, as the\n// fallback it resolved to, so a bad pair costs one failed construction rather\n// than one a render.\nconst formatters = new Map<string, ResolvedFormat>()\n\n// The amount is read off `format` alone, where web reads it off\n// `formatToParts` with the `signDisplay` settings that leave a zero bare.\n// Hermes has neither: it defines no `formatToParts` on iOS, ignores\n// `signDisplay` there, and takes only `auto`, `never`, `always` and\n// `exceptZero` on Android, so the `negative` web asks for throws. `format` is\n// what every runtime React Native runs on has.\n//\n// So the magnitude is formatted on its own, and it shows as zero exactly when\n// it reads as zero formatted by the same formatter, whatever digits the\n// locale writes. A sign, where one is shown, goes where the locale puts its\n// own minus. On a runtime with `formatToParts` the text this writes is the\n// text web writes, which the tests compare across locales.\nfunction formatCurrency(\n value: number,\n options: {\n currency: string\n locale: string | undefined\n sign: CurrencySignDisplay\n },\n) {\n const { code, formatter, zero } = formatterFor(\n options.locale,\n options.currency,\n )\n const magnitude = formatter.format(Math.abs(value))\n // Whether every digit shown is a zero, which is what the tone reads rather\n // than the raw value: the two never saw the same number before.\n const shownAsZero = magnitude === zero\n const mark = signFor(value, options.sign, shownAsZero)\n const amount = mark === undefined ? magnitude : signed(formatter, value, mark)\n\n return {\n shownAsZero,\n text: code === null || code === '' ? amount : `${amount} ${code}`,\n }\n}\n\n// A component that only shows an amount must not take the screen down with\n// it, and `new Intl.NumberFormat` throws for a malformed locale (`xx_YY`) and\n// for a currency code that is not one (`xx`, `US`). Valid input takes exactly\n// the path it always did; the rest falls back. A locale the runtime rejects,\n// an empty one among them, which is what clearing a locale control leaves,\n// gives way to the device's, and a currency code it rejects to the plain\n// number in that locale with the code written after it, so the amount still\n// reads and what was wrong with it stays visible.\n//\n// Nothing is logged: the library writes to the console nowhere, and a\n// warning an app cannot turn off is not its to emit.\nfunction formatterFor(locale: string | undefined, currency: string) {\n // Neither a BCP 47 locale nor an ISO 4217 code can contain a vertical bar,\n // so joining on one cannot make two different pairs share a key.\n const key = `${locale ?? ''}|${currency}`\n const cached = formatters.get(key)\n if (cached !== undefined) {\n return cached\n }\n\n const usable = locale !== undefined && isLocale(locale) ? locale : undefined\n let formatter: Intl.NumberFormat\n let code: null | string = null\n try {\n formatter = new Intl.NumberFormat(usable, { currency, style: 'currency' })\n } catch {\n formatter = new Intl.NumberFormat(usable, {\n maximumFractionDigits: 2,\n minimumFractionDigits: 2,\n })\n code = currency\n }\n\n const resolved = { code, formatter, zero: formatter.format(0) }\n formatters.set(key, resolved)\n return resolved\n}\n\n// Whether the runtime takes `tag` as a locale at all — one it parses, whether\n// or not it has data for it, which it then fills in from the nearest it has.\nfunction isLocale(tag: string) {\n try {\n return Intl.NumberFormat.supportedLocalesOf(tag).length >= 0\n } catch {\n return false\n }\n}\n\n// An amount shown as zero is neither owed nor owing, so it takes the neutral\n// role whatever the raw value's sign: negative zero, a rounded-away debt and\n// float residue all read as settled. Otherwise the sign decides.\nfunction resolveTone(value: number, tone: CurrencyTone, shownAsZero: boolean) {\n if (tone !== 'auto') {\n return tone\n }\n if (shownAsZero) {\n return 'neutral'\n }\n if (value > 0) {\n return 'positive'\n }\n if (value < 0) {\n return 'negative'\n }\n return 'neutral'\n}\n\n// The amount with `mark` in place of the minus the locale writes on a\n// negative, so the mark sits where the locale puts its own sign: before the\n// symbol or after it, beside a direction mark in a right-to-left locale, or in\n// place of a space, as Swiss German's `CHF-12.50` does beside `CHF 12.50`. A\n// plus takes the same place, which is where Intl's own `exceptZero` puts it.\n// Only the sign is replaced: nothing a locale writes ahead of it, a symbol, a\n// code, a space or a direction mark, is a minus, so the first is the sign.\nfunction signed(formatter: Intl.NumberFormat, value: number, mark: string) {\n return formatter.format(-Math.abs(value)).replace(LOCALE_MINUS, mark)\n}\n\n// The sign an amount carries, if any. One shown as zero carries none under\n// any setting, since there is nothing owed or owing; otherwise a negative\n// takes a minus unless told to show none, and a positive a plus only when\n// told to always show one.\nfunction signFor(\n value: number,\n sign: CurrencySignDisplay,\n shownAsZero: boolean,\n) {\n if (shownAsZero || sign === 'never') {\n return undefined\n }\n if (value < 0) {\n return MINUS_SIGN\n }\n if (value > 0 && sign === 'always') {\n return PLUS_SIGN\n }\n return undefined\n}\n\nexport type { CurrencyProps, CurrencySignDisplay, CurrencyTone }\n\nexport { Currency }\n"],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { FormValidationBehavior, FormValues, ValidationErrors } from "../../form/index.js";
|
|
2
|
+
import { ReactNode, Ref } from "react";
|
|
3
|
+
//#region src/components/form/index.d.ts
|
|
4
|
+
/** What a form's ref holds. */
|
|
5
|
+
type FormHandle = {
|
|
6
|
+
/**
|
|
7
|
+
* Submits the form, as a Button with `type="submit"` inside it does: under
|
|
8
|
+
* `native` it is held back while a field blocks it, and otherwise
|
|
9
|
+
* `onSubmit` is handed the values. Named after the web form's method that
|
|
10
|
+
* does the same.
|
|
11
|
+
*/
|
|
12
|
+
readonly requestSubmit: () => void;
|
|
13
|
+
};
|
|
14
|
+
type FormProps = {
|
|
15
|
+
readonly children?: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Called with the values when the form is submitted: each field with a
|
|
18
|
+
* `name` and not disabled, as the string a web form submits for it, so a
|
|
19
|
+
* number field's value is its number written out, and an empty one is
|
|
20
|
+
* `''`. Under `native` it is not called while a field blocks the
|
|
21
|
+
* submission.
|
|
22
|
+
*/
|
|
23
|
+
readonly onSubmit?: (values: FormValues) => void;
|
|
24
|
+
readonly ref?: Ref<FormHandle>;
|
|
25
|
+
/**
|
|
26
|
+
* How the fields inside validate. `aria` marks a field invalid and shows
|
|
27
|
+
* its error as soon as the field says so, through its `validate`, and
|
|
28
|
+
* lets the form submit regardless. `native` holds the submission back
|
|
29
|
+
* while a field is required and empty or rejected by its `validate`, shows
|
|
30
|
+
* each such field's error once submission is tried, and focuses the first
|
|
31
|
+
* of them.
|
|
32
|
+
*
|
|
33
|
+
* Under `aria`, moving focus after a server answers is the call site's.
|
|
34
|
+
* Each error is announced as it appears under its field, but nothing
|
|
35
|
+
* moves, because no part of the form knows a round trip happened:
|
|
36
|
+
* `validationErrors` is a prop rather than an event, so a form cannot tell
|
|
37
|
+
* a submission coming back from a first render that was given errors to
|
|
38
|
+
* begin with. Focus the first field that came back invalid through its
|
|
39
|
+
* `inputRef`, as the Async submit story does.
|
|
40
|
+
* @default 'aria'
|
|
41
|
+
*/
|
|
42
|
+
readonly validationBehavior?: FormValidationBehavior;
|
|
43
|
+
/**
|
|
44
|
+
* Errors to show under the fields, keyed by each field's `name`, such as
|
|
45
|
+
* what a server sent back: one message, or several shown as one line. A
|
|
46
|
+
* field's error stays until the field is edited, and a new object shows
|
|
47
|
+
* them all again.
|
|
48
|
+
*/
|
|
49
|
+
readonly validationErrors?: ValidationErrors;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* A form: the fields inside it and what they are told about validation. Pass
|
|
53
|
+
* `validationErrors`, an object keyed by field `name`, to show what a server
|
|
54
|
+
* sent back under each field, and `validationBehavior="native"` to hold the
|
|
55
|
+
* submission back while a required field is empty or a field's `validate`
|
|
56
|
+
* rejects its value. A Button with `type="submit"` inside it submits it, and
|
|
57
|
+
* `onSubmit` is handed the values by name.
|
|
58
|
+
*
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <Form onSubmit={(values) => save(values)}>
|
|
61
|
+
* <Stack gap="md">
|
|
62
|
+
* <TextField label="Headline" name="headline" />
|
|
63
|
+
* <Button type="submit">Save changes</Button>
|
|
64
|
+
* </Stack>
|
|
65
|
+
* </Form>
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* Handing `validationErrors` back from a submission is where a reader has to
|
|
69
|
+
* be told something arrived: see `validationBehavior` for what the form does
|
|
70
|
+
* and what it leaves to the call site.
|
|
71
|
+
*
|
|
72
|
+
* A form also reserves each field's message line. A field on its own draws
|
|
73
|
+
* that line only once it has something to say, so an error arriving after a
|
|
74
|
+
* submit or an async check makes the field taller and moves everything below
|
|
75
|
+
* it down the screen. Inside a form the line is there from the start, empty,
|
|
76
|
+
* and nothing moves when it fills.
|
|
77
|
+
*
|
|
78
|
+
* It draws nothing, so it takes no `style`; its ref is a handle whose
|
|
79
|
+
* `requestSubmit()` submits it.
|
|
80
|
+
*/
|
|
81
|
+
declare function Form({ children, onSubmit, ref, validationBehavior, validationErrors }: FormProps): import("react").JSX.Element;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { Form, type FormHandle, type FormProps };
|
|
84
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { FIELD_VALIDATION_BEHAVIOR, FormContext, createRegistry, submitOf } from "../../form/index.js";
|
|
2
|
+
import { c } from "react/compiler-runtime";
|
|
3
|
+
import { useImperativeHandle, useState } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/form/index.tsx
|
|
6
|
+
const NO_ERRORS = {};
|
|
7
|
+
/**
|
|
8
|
+
* A form: the fields inside it and what they are told about validation. Pass
|
|
9
|
+
* `validationErrors`, an object keyed by field `name`, to show what a server
|
|
10
|
+
* sent back under each field, and `validationBehavior="native"` to hold the
|
|
11
|
+
* submission back while a required field is empty or a field's `validate`
|
|
12
|
+
* rejects its value. A Button with `type="submit"` inside it submits it, and
|
|
13
|
+
* `onSubmit` is handed the values by name.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <Form onSubmit={(values) => save(values)}>
|
|
17
|
+
* <Stack gap="md">
|
|
18
|
+
* <TextField label="Headline" name="headline" />
|
|
19
|
+
* <Button type="submit">Save changes</Button>
|
|
20
|
+
* </Stack>
|
|
21
|
+
* </Form>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Handing `validationErrors` back from a submission is where a reader has to
|
|
25
|
+
* be told something arrived: see `validationBehavior` for what the form does
|
|
26
|
+
* and what it leaves to the call site.
|
|
27
|
+
*
|
|
28
|
+
* A form also reserves each field's message line. A field on its own draws
|
|
29
|
+
* that line only once it has something to say, so an error arriving after a
|
|
30
|
+
* submit or an async check makes the field taller and moves everything below
|
|
31
|
+
* it down the screen. Inside a form the line is there from the start, empty,
|
|
32
|
+
* and nothing moves when it fills.
|
|
33
|
+
*
|
|
34
|
+
* It draws nothing, so it takes no `style`; its ref is a handle whose
|
|
35
|
+
* `requestSubmit()` submits it.
|
|
36
|
+
*/
|
|
37
|
+
function Form(t0) {
|
|
38
|
+
const $ = c(15);
|
|
39
|
+
const { children, onSubmit, ref, validationBehavior: t1, validationErrors: t2 } = t0;
|
|
40
|
+
const validationBehavior = t1 === void 0 ? FIELD_VALIDATION_BEHAVIOR : t1;
|
|
41
|
+
const validationErrors = t2 === void 0 ? NO_ERRORS : t2;
|
|
42
|
+
const [registry] = useState(createRegistry);
|
|
43
|
+
let t3;
|
|
44
|
+
if ($[0] !== onSubmit || $[1] !== registry || $[2] !== validationBehavior) {
|
|
45
|
+
t3 = submitOf(registry, validationBehavior, onSubmit);
|
|
46
|
+
$[0] = onSubmit;
|
|
47
|
+
$[1] = registry;
|
|
48
|
+
$[2] = validationBehavior;
|
|
49
|
+
$[3] = t3;
|
|
50
|
+
} else t3 = $[3];
|
|
51
|
+
const requestSubmit = t3;
|
|
52
|
+
let t4;
|
|
53
|
+
if ($[4] !== registry || $[5] !== requestSubmit || $[6] !== validationBehavior || $[7] !== validationErrors) {
|
|
54
|
+
t4 = {
|
|
55
|
+
registry,
|
|
56
|
+
requestSubmit,
|
|
57
|
+
validationBehavior,
|
|
58
|
+
validationErrors
|
|
59
|
+
};
|
|
60
|
+
$[4] = registry;
|
|
61
|
+
$[5] = requestSubmit;
|
|
62
|
+
$[6] = validationBehavior;
|
|
63
|
+
$[7] = validationErrors;
|
|
64
|
+
$[8] = t4;
|
|
65
|
+
} else t4 = $[8];
|
|
66
|
+
const state = t4;
|
|
67
|
+
let t5;
|
|
68
|
+
let t6;
|
|
69
|
+
if ($[9] !== requestSubmit) {
|
|
70
|
+
t5 = () => ({ requestSubmit });
|
|
71
|
+
t6 = [requestSubmit];
|
|
72
|
+
$[9] = requestSubmit;
|
|
73
|
+
$[10] = t5;
|
|
74
|
+
$[11] = t6;
|
|
75
|
+
} else {
|
|
76
|
+
t5 = $[10];
|
|
77
|
+
t6 = $[11];
|
|
78
|
+
}
|
|
79
|
+
useImperativeHandle(ref, t5, t6);
|
|
80
|
+
let t7;
|
|
81
|
+
if ($[12] !== children || $[13] !== state) {
|
|
82
|
+
t7 = /* @__PURE__ */ jsx(FormContext, {
|
|
83
|
+
value: state,
|
|
84
|
+
children
|
|
85
|
+
});
|
|
86
|
+
$[12] = children;
|
|
87
|
+
$[13] = state;
|
|
88
|
+
$[14] = t7;
|
|
89
|
+
} else t7 = $[14];
|
|
90
|
+
return t7;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { Form };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["ReactNode","Ref","useImperativeHandle","useMemo","useState","FormValidationBehavior","FormValues","ValidationErrors","createRegistry","FIELD_VALIDATION_BEHAVIOR","FormContext","submitOf","FormHandle","requestSubmit","FormProps","children","onSubmit","values","ref","validationBehavior","validationErrors","NO_ERRORS","Form","t0","$","_c","t1","t2","undefined","registry","t3","t4","state","t5","t6","t7","_c"],"sources":["../../../src/components/form/index.tsx"],"sourcesContent":["import type { ReactNode, Ref } from 'react'\n\nimport { useImperativeHandle, useMemo, useState } from 'react'\n\nimport type {\n FormValidationBehavior,\n FormValues,\n ValidationErrors,\n} from '../../form'\n\nimport {\n createRegistry,\n FIELD_VALIDATION_BEHAVIOR,\n FormContext,\n submitOf,\n} from '../../form'\n\n// No Material Design page of its own, as on web: a form is the fields inside\n// it, and the text fields page is what each of them draws. The error a form\n// reports lands in the field's supporting text, in the error role, under the\n// box.\n//\n// Native has no form element, so this is a context around its fields rather\n// than an element of its own. It draws nothing and lays nothing out; a Stack\n// inside it is what spaces the fields. It hands each field inside it two\n// things, as React Aria's Form does on web: `validationErrors`, keyed by each\n// field's `name`, which is how a server's answer reaches the fields without\n// any of them changing, and `validationBehavior`, which every field reads in\n// place of its own. And it reserves each field's message line, so an error\n// that arrives later moves nothing below the field.\n//\n// Native has no browser to validate or submit either, so the form does both.\n// A Button with `type=\"submit\"` inside it submits it, as does\n// `requestSubmit()` on its ref, which is how the keyboard's return key does.\n\n/** What a form's ref holds. */\ntype FormHandle = {\n /**\n * Submits the form, as a Button with `type=\"submit\"` inside it does: under\n * `native` it is held back while a field blocks it, and otherwise\n * `onSubmit` is handed the values. Named after the web form's method that\n * does the same.\n */\n readonly requestSubmit: () => void\n}\n\ntype FormProps = {\n readonly children?: ReactNode\n /**\n * Called with the values when the form is submitted: each field with a\n * `name` and not disabled, as the string a web form submits for it, so a\n * number field's value is its number written out, and an empty one is\n * `''`. Under `native` it is not called while a field blocks the\n * submission.\n */\n readonly onSubmit?: (values: FormValues) => void\n readonly ref?: Ref<FormHandle>\n /**\n * How the fields inside validate. `aria` marks a field invalid and shows\n * its error as soon as the field says so, through its `validate`, and\n * lets the form submit regardless. `native` holds the submission back\n * while a field is required and empty or rejected by its `validate`, shows\n * each such field's error once submission is tried, and focuses the first\n * of them.\n *\n * Under `aria`, moving focus after a server answers is the call site's.\n * Each error is announced as it appears under its field, but nothing\n * moves, because no part of the form knows a round trip happened:\n * `validationErrors` is a prop rather than an event, so a form cannot tell\n * a submission coming back from a first render that was given errors to\n * begin with. Focus the first field that came back invalid through its\n * `inputRef`, as the Async submit story does.\n * @default 'aria'\n */\n readonly validationBehavior?: FormValidationBehavior\n /**\n * Errors to show under the fields, keyed by each field's `name`, such as\n * what a server sent back: one message, or several shown as one line. A\n * field's error stays until the field is edited, and a new object shows\n * them all again.\n */\n readonly validationErrors?: ValidationErrors\n}\n\nconst NO_ERRORS: ValidationErrors = {}\n\n/**\n * A form: the fields inside it and what they are told about validation. Pass\n * `validationErrors`, an object keyed by field `name`, to show what a server\n * sent back under each field, and `validationBehavior=\"native\"` to hold the\n * submission back while a required field is empty or a field's `validate`\n * rejects its value. A Button with `type=\"submit\"` inside it submits it, and\n * `onSubmit` is handed the values by name.\n *\n * ```tsx\n * <Form onSubmit={(values) => save(values)}>\n * <Stack gap=\"md\">\n * <TextField label=\"Headline\" name=\"headline\" />\n * <Button type=\"submit\">Save changes</Button>\n * </Stack>\n * </Form>\n * ```\n *\n * Handing `validationErrors` back from a submission is where a reader has to\n * be told something arrived: see `validationBehavior` for what the form does\n * and what it leaves to the call site.\n *\n * A form also reserves each field's message line. A field on its own draws\n * that line only once it has something to say, so an error arriving after a\n * submit or an async check makes the field taller and moves everything below\n * it down the screen. Inside a form the line is there from the start, empty,\n * and nothing moves when it fills.\n *\n * It draws nothing, so it takes no `style`; its ref is a handle whose\n * `requestSubmit()` submits it.\n */\nfunction Form({\n children,\n onSubmit,\n ref,\n validationBehavior = FIELD_VALIDATION_BEHAVIOR,\n validationErrors = NO_ERRORS,\n}: FormProps) {\n const [registry] = useState(createRegistry)\n const requestSubmit = submitOf(registry, validationBehavior, onSubmit)\n const state = useMemo(\n () => ({ registry, requestSubmit, validationBehavior, validationErrors }),\n [registry, requestSubmit, validationBehavior, validationErrors],\n )\n\n useImperativeHandle(ref, () => ({ requestSubmit }), [requestSubmit])\n\n return <FormContext value={state}>{children}</FormContext>\n}\n\nexport { Form }\nexport type { FormHandle, FormProps }\n"],"mappings":";;;;;AAoFA,MAAMqB,YAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCrC,SAAAC,KAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAAc,MAAA,EAAAV,UAAAC,UAAAE,KAAAC,oBAAAO,IAAAN,kBAAAO,OAAAJ;CAIZ,MAAAJ,qBAAAO,OAAAE,KAAAA,IAAAnB,4BAAAiB;CACA,MAAAN,mBAAAO,OAAAC,KAAAA,IAAAP,YAAAM;CAEA,MAAA,CAAAE,YAAmBzB,SAASI,cAAc;CAAC,IAAAsB;CAAA,IAAAN,EAAA,OAAAR,YAAAQ,EAAA,OAAAK,YAAAL,EAAA,OAAAL,oBAAA;EACrBW,KAAAnB,SAASkB,UAAUV,oBAAoBH,QAAQ;EAACQ,EAAA,KAAAR;EAAAQ,EAAA,KAAAK;EAAAL,EAAA,KAAAL;EAAAK,EAAA,KAAAM;CAAA,OAAAA,KAAAN,EAAA;CAAtE,MAAAX,gBAAsBiB;CAAgD,IAAAC;CAAA,IAAAP,EAAA,OAAAK,YAAAL,EAAA,OAAAX,iBAAAW,EAAA,OAAAL,sBAAAK,EAAA,OAAAJ,kBAAA;EAE7DW,KAAA;GAAAF;GAAAhB;GAAAM;GAAAC;EAAgE;EAACI,EAAA,KAAAK;EAAAL,EAAA,KAAAX;EAAAW,EAAA,KAAAL;EAAAK,EAAA,KAAAJ;EAAAI,EAAA,KAAAO;CAAA,OAAAA,KAAAP,EAAA;CAD1E,MAAAQ,QACSD;CAER,IAAAE;CAAA,IAAAC;CAAA,IAAAV,EAAA,OAAAX,eAAA;EAEwBoB,YAAO,EAAApB,cAAgB;EAAIqB,KAAA,CAACrB,aAAa;EAACW,EAAA,KAAAX;EAAAW,EAAA,MAAAS;EAAAT,EAAA,MAAAU;CAAA,OAAA;EAAAD,KAAAT,EAAA;EAAAU,KAAAV,EAAA;CAAA;CAAnEtB,oBAAoBgB,KAAKe,IAA2BC,EAAe;CAAC,IAAAC;CAAA,IAAAX,EAAA,QAAAT,YAAAS,EAAA,QAAAQ,OAAA;EAE7DG,KAAA,oBAAC,aAAD;GAAoBH,OAAAA;GAAQjB;EAAhB,CAAA;EAAuCS,EAAA,MAAAT;EAAAS,EAAA,MAAAQ;EAAAR,EAAA,MAAAW;CAAA,OAAAA,KAAAX,EAAA;CAAA,OAAnDW;AAAmD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ButtonLinkForm, ButtonPressForm, ButtonToggleForm } from "../../button/index.js";
|
|
2
|
+
import { PressableProps, StyleProp, View, ViewStyle } from "react-native";
|
|
3
|
+
import { ReactNode, Ref } from "react";
|
|
4
|
+
//#region src/components/icon-button/index.d.ts
|
|
5
|
+
type IconButtonProps = (ButtonLinkForm | ButtonPressForm | ButtonToggleForm) & Omit<PressableProps, 'aria-label' | 'children' | 'disabled' | 'style'> & {
|
|
6
|
+
/**
|
|
7
|
+
* What the button does, in words. Required rather than optional: an icon
|
|
8
|
+
* on its own has no accessible name, so without this the control
|
|
9
|
+
* announces nothing at all.
|
|
10
|
+
*/
|
|
11
|
+
readonly 'aria-label': string;
|
|
12
|
+
/**
|
|
13
|
+
* The icon. The button hands it its colour and the page's icon size for
|
|
14
|
+
* the button's size through `useIconStyle()`.
|
|
15
|
+
*/
|
|
16
|
+
readonly children?: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Draws no press ripple. The state layer is unaffected.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
readonly disableRipple?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Draws the button disabled, and takes no presses.
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
readonly isDisabled?: boolean;
|
|
27
|
+
readonly ref?: Ref<View>;
|
|
28
|
+
/**
|
|
29
|
+
* Control size: `xs` 32dp, `md` 40dp, `lg` 56dp, `xl` 96dp, `xxl` 136dp,
|
|
30
|
+
* the icon buttons spec page's XS to XL, and the same heights `Button`
|
|
31
|
+
* uses, so the two line up beside each other in a row. The icon is 20dp,
|
|
32
|
+
* 24dp, 24dp, 32dp and 40dp in turn.
|
|
33
|
+
* @default 'md'
|
|
34
|
+
*/
|
|
35
|
+
readonly size?: IconButtonSize;
|
|
36
|
+
readonly style?: StyleProp<ViewStyle>;
|
|
37
|
+
/**
|
|
38
|
+
* `standard` is transparent and tints what it sits on; `outlined` is
|
|
39
|
+
* transparent with a rule around it; `filled` and `tonal` carry a
|
|
40
|
+
* container of their own.
|
|
41
|
+
* @default 'standard'
|
|
42
|
+
*/
|
|
43
|
+
readonly variant?: IconButtonVariant;
|
|
44
|
+
};
|
|
45
|
+
type IconButtonSize = 'lg' | 'md' | 'xl' | 'xs' | 'xxl';
|
|
46
|
+
type IconButtonVariant = 'filled' | 'outlined' | 'standard' | 'tonal';
|
|
47
|
+
/**
|
|
48
|
+
* A button that is an icon, at five control heights, with web's props and
|
|
49
|
+
* values. Given `href` it is a link with the same appearance, and given
|
|
50
|
+
* `isSelected`, `defaultSelected` or `onChange` it is a toggle, which reports
|
|
51
|
+
* its state to a screen reader and draws the page's second pair of colour
|
|
52
|
+
* roles for its variant. The three forms are exclusive, so a toggle given
|
|
53
|
+
* `href` does not compile.
|
|
54
|
+
*
|
|
55
|
+
* ```tsx
|
|
56
|
+
* <IconButton aria-label="Label" defaultSelected variant="tonal">
|
|
57
|
+
* <Icon />
|
|
58
|
+
* </IconButton>
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* The two sizes under 48dp, `xs` and `md`, take presses over a 48dp target
|
|
62
|
+
* through `hitSlop`, which a `hitSlop` of the call site's own replaces.
|
|
63
|
+
* `style`, `ref` and `testID` land on the `Pressable`.
|
|
64
|
+
*/
|
|
65
|
+
declare function IconButton({ children, defaultSelected, disableRipple, hitSlop: ownHitSlop, href, isDisabled, isSelected, onBlur, onChange, onFocus, onHoverIn, onHoverOut, onLayout, onPress, onPressIn, onPressOut, size, style, type, variant, ...props }: IconButtonProps): import("react").JSX.Element;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { IconButton, type IconButtonProps, type IconButtonSize, type IconButtonVariant };
|
|
68
|
+
//# sourceMappingURL=index.d.ts.map
|