expo-interface 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -10
- package/package.json +6 -1
- package/src/accent.tsx +21 -8
- package/src/button/button.css +6 -0
- package/src/button/index.android.tsx +5 -1
- package/src/button/index.ios.tsx +5 -1
- package/src/button/index.tsx +6 -1
- package/src/button/types.ts +13 -0
- package/src/color-picker/color-picker.css +70 -0
- package/src/color-picker/index.android.tsx +77 -49
- package/src/color-picker/index.ios.tsx +58 -11
- package/src/color-picker/index.tsx +65 -15
- package/src/color-picker/types.ts +9 -3
- package/src/context-menu/index.android.tsx +39 -11
- package/src/context-menu/index.ios.tsx +3 -1
- package/src/context-menu/index.tsx +24 -7
- package/src/fab/fab.css +72 -0
- package/src/fab/index.android.tsx +72 -0
- package/src/fab/index.ios.tsx +68 -0
- package/src/fab/index.tsx +37 -0
- package/src/fab/shared.ts +23 -0
- package/src/fab/types.ts +39 -0
- package/src/field-group/index.android.tsx +22 -16
- package/src/field-group/index.tsx +36 -5
- package/src/field-group/index.web.tsx +25 -9
- package/src/field-group/shared.tsx +49 -0
- package/src/field-group/types.ts +25 -0
- package/src/header-menu/index.tsx +76 -0
- package/src/host/index.tsx +32 -0
- package/src/index.ts +31 -5
- package/src/keyboard/index.tsx +81 -0
- package/src/keyboard/library.native.ts +17 -0
- package/src/keyboard/library.ts +12 -0
- package/src/keyboard/types.ts +20 -0
- package/src/list-item/index.android.tsx +23 -6
- package/src/list-item/index.tsx +20 -4
- package/src/list-item/index.web.tsx +60 -0
- package/src/list-item/list-item.css +65 -0
- package/src/list-item/types.ts +21 -0
- package/src/menu/index.android.tsx +22 -9
- package/src/menu/index.ios.tsx +26 -11
- package/src/menu/index.tsx +26 -10
- package/src/menu/list.tsx +25 -10
- package/src/menu/menu.css +52 -0
- package/src/menu/types.ts +41 -2
- package/src/scheme.ts +140 -0
- package/src/screen/index.tsx +49 -5
- package/src/tab-stack/index.tsx +15 -2
- package/src/tabs/index.tsx +2 -1
- package/src/tabs/index.web.tsx +29 -4
- package/src/tabs/types.ts +20 -2
- package/src/text-field/index.android.tsx +30 -6
- package/src/text-field/index.ios.tsx +16 -3
- package/src/text-field/index.tsx +24 -4
- package/src/text-field/inline.tsx +87 -0
- package/src/text-field/shared.ts +40 -1
- package/src/text-field/types.ts +47 -2
- package/src/theme.ts +44 -5
package/src/text-field/index.tsx
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import type {TextFieldProps} from './types';
|
|
2
|
-
import type {TextStyle} from 'react-native';
|
|
2
|
+
import type {NativeSyntheticEvent, TextInputKeyPressEventData, TextStyle} from 'react-native';
|
|
3
3
|
|
|
4
|
+
import {useRef} from 'react';
|
|
4
5
|
import {StyleSheet, TextInput} from 'react-native';
|
|
5
6
|
import {fonts, fontWeights, theme, variants} from '../theme';
|
|
6
|
-
import {
|
|
7
|
+
import {InlineTextField} from './inline';
|
|
8
|
+
import {keyboardTypeFor, useAutoFocus, useTextValue} from './shared';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `row` variant is the form row below; `inline` is the borderless field
|
|
12
|
+
* for a React Native layout (the same `TextInput`, sized to its room).
|
|
13
|
+
*/
|
|
14
|
+
export function TextField(props: TextFieldProps) {
|
|
15
|
+
if (props.variant === 'inline') return <InlineTextField {...props}/>;
|
|
16
|
+
return <RowTextField {...props}/>;
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
20
|
* On web the field mirrors the native iOS/Android row: a borderless, full-width
|
|
@@ -11,11 +22,12 @@ import {keyboardTypeFor, useTextValue} from './shared';
|
|
|
11
22
|
* typography so it sits flush inside a `FieldGroup.Section`. The browser focus
|
|
12
23
|
* outline is suppressed to match the chromeless iOS `Form` look.
|
|
13
24
|
*/
|
|
14
|
-
|
|
25
|
+
function RowTextField({
|
|
15
26
|
placeholder,
|
|
16
27
|
value,
|
|
17
28
|
onChangeText,
|
|
18
29
|
onSubmit,
|
|
30
|
+
onKeyPress,
|
|
19
31
|
disabled,
|
|
20
32
|
secureTextEntry,
|
|
21
33
|
keyboardType,
|
|
@@ -23,16 +35,21 @@ export function TextField({
|
|
|
23
35
|
autoCorrect,
|
|
24
36
|
multiline,
|
|
25
37
|
autoFocus,
|
|
38
|
+
returnKeyType,
|
|
39
|
+
submitBehavior,
|
|
26
40
|
maxLength,
|
|
27
41
|
accentColor,
|
|
28
42
|
testID,
|
|
29
43
|
style,
|
|
30
44
|
}: TextFieldProps) {
|
|
45
|
+
const input = useRef<TextInput>(null);
|
|
31
46
|
const [current, setValue] = useTextValue(value, onChangeText);
|
|
32
47
|
const cursor = accentColor ?? (theme.tint as string);
|
|
48
|
+
useAutoFocus(input, autoFocus);
|
|
33
49
|
|
|
34
50
|
return (
|
|
35
51
|
<TextInput
|
|
52
|
+
ref={input}
|
|
36
53
|
value={current}
|
|
37
54
|
onChangeText={setValue}
|
|
38
55
|
placeholder={placeholder}
|
|
@@ -43,11 +60,14 @@ export function TextField({
|
|
|
43
60
|
autoCapitalize={autoCapitalize}
|
|
44
61
|
autoCorrect={autoCorrect}
|
|
45
62
|
multiline={multiline}
|
|
46
|
-
autoFocus={autoFocus}
|
|
47
63
|
maxLength={maxLength}
|
|
48
64
|
cursorColor={cursor}
|
|
49
65
|
selectionColor={cursor}
|
|
66
|
+
returnKeyType={returnKeyType}
|
|
67
|
+
submitBehavior={submitBehavior}
|
|
50
68
|
onSubmitEditing={onSubmit ? event => onSubmit(event.nativeEvent.text) : undefined}
|
|
69
|
+
onKeyPress={onKeyPress ? (event: NativeSyntheticEvent<TextInputKeyPressEventData & {shiftKey?: boolean}>) =>
|
|
70
|
+
onKeyPress(event.nativeEvent.key, event.nativeEvent.shiftKey === true) : undefined}
|
|
51
71
|
aria-label={placeholder}
|
|
52
72
|
testID={testID}
|
|
53
73
|
style={[styles.input, disabled && styles.disabled, style]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {NativeSyntheticEvent, TextInputKeyPressEventData} from 'react-native';
|
|
2
|
+
import type {TextFieldProps} from './types';
|
|
3
|
+
import {useRef} from 'react';
|
|
4
|
+
import {StyleSheet, TextInput} from 'react-native';
|
|
5
|
+
import {fonts, fontWeights, spacing, useColor} from '../theme';
|
|
6
|
+
import {keyboardTypeFor, useAutoFocus, useTextValue} from './shared';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The `inline` variant: a borderless React Native `TextInput` on every
|
|
10
|
+
* platform, for a field that sits inside a React Native layout (a search
|
|
11
|
+
* row in a toolbar, a prompt in a bar) where the native form control would
|
|
12
|
+
* need a host of its own. It grows to the room it is given, focuses on
|
|
13
|
+
* mount when asked (and makes sure the keyboard came on Android), and steps
|
|
14
|
+
* with the keyboard's action key (`returnKeyType` with `submitBehavior`).
|
|
15
|
+
*/
|
|
16
|
+
export function InlineTextField({
|
|
17
|
+
placeholder,
|
|
18
|
+
value,
|
|
19
|
+
onChangeText,
|
|
20
|
+
onSubmit,
|
|
21
|
+
onKeyPress,
|
|
22
|
+
disabled,
|
|
23
|
+
secureTextEntry,
|
|
24
|
+
keyboardType,
|
|
25
|
+
autoCapitalize,
|
|
26
|
+
autoCorrect,
|
|
27
|
+
multiline,
|
|
28
|
+
autoFocus,
|
|
29
|
+
returnKeyType,
|
|
30
|
+
submitBehavior,
|
|
31
|
+
maxLength,
|
|
32
|
+
accentColor,
|
|
33
|
+
testID,
|
|
34
|
+
style,
|
|
35
|
+
}: TextFieldProps) {
|
|
36
|
+
const input = useRef<TextInput>(null);
|
|
37
|
+
const [current, setValue] = useTextValue(value, onChangeText);
|
|
38
|
+
const label = useColor('label');
|
|
39
|
+
const placeholderColor = useColor('tertiaryLabel');
|
|
40
|
+
const tint = useColor('tint');
|
|
41
|
+
const cursor = accentColor ?? tint;
|
|
42
|
+
useAutoFocus(input, autoFocus);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<TextInput
|
|
46
|
+
ref={input}
|
|
47
|
+
value={current}
|
|
48
|
+
onChangeText={setValue}
|
|
49
|
+
placeholder={placeholder}
|
|
50
|
+
placeholderTextColor={placeholderColor}
|
|
51
|
+
editable={!disabled}
|
|
52
|
+
secureTextEntry={secureTextEntry}
|
|
53
|
+
keyboardType={keyboardTypeFor(keyboardType)}
|
|
54
|
+
autoCapitalize={autoCapitalize}
|
|
55
|
+
autoCorrect={autoCorrect}
|
|
56
|
+
multiline={multiline}
|
|
57
|
+
maxLength={maxLength}
|
|
58
|
+
cursorColor={cursor}
|
|
59
|
+
selectionColor={cursor}
|
|
60
|
+
returnKeyType={returnKeyType}
|
|
61
|
+
submitBehavior={submitBehavior}
|
|
62
|
+
onSubmitEditing={onSubmit ? event => onSubmit(event.nativeEvent.text) : undefined}
|
|
63
|
+
onKeyPress={onKeyPress ? (event: NativeSyntheticEvent<TextInputKeyPressEventData & {shiftKey?: boolean}>) =>
|
|
64
|
+
onKeyPress(event.nativeEvent.key, event.nativeEvent.shiftKey === true) : undefined}
|
|
65
|
+
aria-label={placeholder}
|
|
66
|
+
testID={testID}
|
|
67
|
+
style={[styles.input, {color: label}, disabled && styles.disabled, style]}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const styles = StyleSheet.create({
|
|
73
|
+
input: {
|
|
74
|
+
flexGrow: 1,
|
|
75
|
+
flexShrink: 1,
|
|
76
|
+
minWidth: 0,
|
|
77
|
+
margin: 0,
|
|
78
|
+
paddingVertical: spacing.one,
|
|
79
|
+
paddingHorizontal: spacing.two,
|
|
80
|
+
fontSize: 14,
|
|
81
|
+
fontFamily: fonts?.sans,
|
|
82
|
+
fontWeight: fontWeights.normal,
|
|
83
|
+
},
|
|
84
|
+
disabled: {
|
|
85
|
+
opacity: 0.4,
|
|
86
|
+
},
|
|
87
|
+
});
|
package/src/text-field/shared.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {RefObject} from 'react';
|
|
2
|
+
import type {TextInput} from 'react-native';
|
|
2
3
|
import type {ObservableState} from '@expo/ui';
|
|
4
|
+
import type {TextFieldKeyboard} from './types';
|
|
3
5
|
import {useCallback, useEffect, useState} from 'react';
|
|
6
|
+
import {Keyboard, Platform} from 'react-native';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Keyboard variants understood by both React Native's `keyboardType` prop and
|
|
@@ -14,6 +17,9 @@ type AppleKeyboardType =
|
|
|
14
17
|
| 'decimal-pad'
|
|
15
18
|
| 'url';
|
|
16
19
|
|
|
20
|
+
/** How long after the mount the keyboard is looked for (`focusField`). */
|
|
21
|
+
export const FOCUS_RETRY_MS = 150;
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* Bridges controlled and uncontrolled usage on web, mirroring `useDateValue`.
|
|
19
25
|
* When `value` is provided the component is controlled; otherwise it falls back
|
|
@@ -83,3 +89,36 @@ export function keyboardTypeFor(type: TextFieldKeyboard | undefined): AppleKeybo
|
|
|
83
89
|
return 'default';
|
|
84
90
|
}
|
|
85
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Focuses a React Native `TextInput` that just mounted, and makes sure its
|
|
95
|
+
* keyboard came: on Android the first focus asks for the keyboard before the
|
|
96
|
+
* field is laid out and served by the input method ("Ignoring
|
|
97
|
+
* showSoftInput() as view is not served"), which leaves a caret in the field
|
|
98
|
+
* and no keyboard; and a second `focus()` on a focused field is a no-op in
|
|
99
|
+
* React Native. So a moment later, if the keyboard is still down, the field
|
|
100
|
+
* is blurred and focused again, a fresh request the input method takes.
|
|
101
|
+
* Returns the effect's cleanup.
|
|
102
|
+
*/
|
|
103
|
+
export function focusField(input: RefObject<TextInput | null>): () => void {
|
|
104
|
+
input.current?.focus();
|
|
105
|
+
if (Platform.OS !== 'android') return () => undefined;
|
|
106
|
+
const again = setTimeout(() => {
|
|
107
|
+
const field = input.current;
|
|
108
|
+
if (!field || Keyboard.isVisible()) return;
|
|
109
|
+
field.blur();
|
|
110
|
+
field.focus();
|
|
111
|
+
}, FOCUS_RETRY_MS);
|
|
112
|
+
return () => clearTimeout(again);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Focuses the field once it is mounted when `autoFocus` is set (see
|
|
117
|
+
* `focusField`), for the React Native based fields (web, `inline`).
|
|
118
|
+
*/
|
|
119
|
+
export function useAutoFocus(input: RefObject<TextInput | null>, autoFocus: boolean | undefined): void {
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!autoFocus) return;
|
|
122
|
+
return focusField(input);
|
|
123
|
+
}, [autoFocus, input]);
|
|
124
|
+
}
|
package/src/text-field/types.ts
CHANGED
|
@@ -12,6 +12,25 @@ export type TextFieldKeyboard =
|
|
|
12
12
|
/** Automatic capitalization behaviour while typing. */
|
|
13
13
|
export type TextFieldCapitalize = 'none' | 'sentences' | 'words' | 'characters';
|
|
14
14
|
|
|
15
|
+
/** What the keyboard's action key says, and reports through `onSubmit`. */
|
|
16
|
+
export type TextFieldReturnKey = 'done' | 'go' | 'next' | 'search' | 'send';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What happens to the focus when the action key is pressed. `blurAndSubmit`
|
|
20
|
+
* closes the keyboard; `submit` keeps the field focused so the next press
|
|
21
|
+
* submits again (an inline search stepping through its matches).
|
|
22
|
+
*/
|
|
23
|
+
export type TextFieldSubmitBehavior = 'blurAndSubmit' | 'submit';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How the field is drawn. `row` is the native form row (SwiftUI `TextField`,
|
|
27
|
+
* a Compose `TextField` stripped of its container) meant for a
|
|
28
|
+
* `FieldGroup.Section`; `inline` is a borderless React Native `TextInput` on
|
|
29
|
+
* every platform, for a field that sits inside a React Native layout (a
|
|
30
|
+
* search row in a toolbar) where the native control would need a host.
|
|
31
|
+
*/
|
|
32
|
+
export type TextFieldVariant = 'row' | 'inline';
|
|
33
|
+
|
|
15
34
|
/**
|
|
16
35
|
* Cross-platform single/multi-line text input with a conformed iOS-style
|
|
17
36
|
* appearance — a borderless field whose placeholder doubles as the row label,
|
|
@@ -30,6 +49,12 @@ export interface TextFieldProps {
|
|
|
30
49
|
onChangeText?: (text: string) => void;
|
|
31
50
|
/** Called when the user presses the keyboard return key. Receives the text. */
|
|
32
51
|
onSubmit?: (text: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Called on a key press with the key's name (`Enter`, `Escape`, `a`) and
|
|
54
|
+
* whether Shift was held, for keyboard handling the platform does not
|
|
55
|
+
* cover. `inline` variant only.
|
|
56
|
+
*/
|
|
57
|
+
onKeyPress?: (key: string, shiftKey: boolean) => void;
|
|
33
58
|
/** Disables editing and dims the field. */
|
|
34
59
|
disabled?: boolean;
|
|
35
60
|
/** Masks the input for sensitive values such as passwords. */
|
|
@@ -51,14 +76,34 @@ export interface TextFieldProps {
|
|
|
51
76
|
autoCorrect?: boolean;
|
|
52
77
|
/** Allows multiple lines of input that grow vertically. */
|
|
53
78
|
multiline?: boolean;
|
|
54
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Focuses the field once it is mounted, and makes sure its keyboard came:
|
|
81
|
+
* on Android the first focus can be asked for before the field is served
|
|
82
|
+
* by the input method and dropped, so a moment later, if the keyboard is
|
|
83
|
+
* still down, the field is focused again.
|
|
84
|
+
*/
|
|
55
85
|
autoFocus?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* What the keyboard's action key says. The key reports through `onSubmit`.
|
|
88
|
+
* @default 'done'
|
|
89
|
+
*/
|
|
90
|
+
returnKeyType?: TextFieldReturnKey;
|
|
91
|
+
/**
|
|
92
|
+
* Whether the action key closes the keyboard.
|
|
93
|
+
* @default 'blurAndSubmit'
|
|
94
|
+
*/
|
|
95
|
+
submitBehavior?: TextFieldSubmitBehavior;
|
|
96
|
+
/**
|
|
97
|
+
* The field's look.
|
|
98
|
+
* @default 'row'
|
|
99
|
+
*/
|
|
100
|
+
variant?: TextFieldVariant;
|
|
56
101
|
/** Maximum number of characters allowed. Truncates natively as the user types. */
|
|
57
102
|
maxLength?: number;
|
|
58
103
|
/** Tint applied to the cursor/selection (web/android) and the field (iOS). */
|
|
59
104
|
accentColor?: string;
|
|
60
105
|
/** Identifier used to locate the component in end-to-end tests. */
|
|
61
106
|
testID?: string;
|
|
62
|
-
/** Style applied to the text content (web
|
|
107
|
+
/** Style applied to the text content (web and the `inline` variant). */
|
|
63
108
|
style?: StyleProp<TextStyle>;
|
|
64
109
|
}
|
package/src/theme.ts
CHANGED
|
@@ -2,14 +2,18 @@ import './global.css';
|
|
|
2
2
|
import type {CSSProperties} from 'react';
|
|
3
3
|
import type {ColorValue, TextStyle} from 'react-native';
|
|
4
4
|
|
|
5
|
+
import {useMemo} from 'react';
|
|
5
6
|
import {DefaultTheme} from 'expo-router';
|
|
6
|
-
import {Platform, PlatformColor
|
|
7
|
+
import {Platform, PlatformColor} from 'react-native';
|
|
7
8
|
import {TypographyVariant, TypographyStyle} from './typography/types';
|
|
8
9
|
import {ACCENT_SEED, onAccent, useAccentSeed} from './accent';
|
|
10
|
+
import {useColorScheme} from './scheme';
|
|
9
11
|
|
|
10
12
|
export type VariantMap = Record<TypographyVariant, TypographyStyle>;
|
|
11
13
|
export type ColorTokens = keyof typeof colors[keyof typeof colors];
|
|
12
14
|
export type ColorValues = typeof colors[keyof typeof colors];
|
|
15
|
+
/** A resolved palette: one plain color string per token (see `usePalette`). */
|
|
16
|
+
export type Palette = Record<ColorTokens, string>;
|
|
13
17
|
export type ColorNative = ColorValue | (() => ColorValue);
|
|
14
18
|
|
|
15
19
|
export const VALID_STYLES = [
|
|
@@ -378,7 +382,7 @@ export function useNavTheme() {
|
|
|
378
382
|
if (Platform.OS === 'web') return nav;
|
|
379
383
|
/* eslint-disable react-hooks/rules-of-hooks -- Platform.OS is a runtime constant. */
|
|
380
384
|
const seed = useAccentSeed();
|
|
381
|
-
const palette = colors[useColorScheme()
|
|
385
|
+
const palette = colors[useColorScheme()];
|
|
382
386
|
/* eslint-enable react-hooks/rules-of-hooks */
|
|
383
387
|
return {
|
|
384
388
|
...nav,
|
|
@@ -445,13 +449,30 @@ export function useColor(token: ColorTokens): string {
|
|
|
445
449
|
if (Platform.OS === 'web') return theme[token] as string;
|
|
446
450
|
/* eslint-disable react-hooks/rules-of-hooks -- Platform.OS is a runtime constant. */
|
|
447
451
|
const seed = useAccentSeed();
|
|
448
|
-
const scheme = useColorScheme()
|
|
452
|
+
const scheme = useColorScheme();
|
|
449
453
|
/* eslint-enable react-hooks/rules-of-hooks */
|
|
450
454
|
if (token === 'tint') return seed;
|
|
451
455
|
if (token === 'onTint') return onAccent(seed);
|
|
452
456
|
return colors[scheme][token];
|
|
453
457
|
}
|
|
454
458
|
|
|
459
|
+
/**
|
|
460
|
+
* The resolved palette of the current scheme, with the live accent seed as
|
|
461
|
+
* `tint` and its contrast as `onTint`: plain color strings on every platform,
|
|
462
|
+
* including web. `useColor` stays the right call for styles (on web it hands
|
|
463
|
+
* out the CSS variable, which follows the scheme without a re-render);
|
|
464
|
+
* `usePalette` is for canvases, native views and anything else that cannot
|
|
465
|
+
* read a variable.
|
|
466
|
+
*/
|
|
467
|
+
export function usePalette(): Palette {
|
|
468
|
+
const seed = useAccentSeed();
|
|
469
|
+
const scheme = useColorScheme();
|
|
470
|
+
return useMemo(
|
|
471
|
+
() => ({...colors[scheme], tint: seed, onTint: onAccent(seed)}),
|
|
472
|
+
[scheme, seed],
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
455
476
|
export function getPlatformToken(specifics: {
|
|
456
477
|
default: ColorValue;
|
|
457
478
|
android: ColorNative;
|
|
@@ -471,10 +492,20 @@ export function getPlatformToken(specifics: {
|
|
|
471
492
|
}
|
|
472
493
|
}
|
|
473
494
|
|
|
495
|
+
/**
|
|
496
|
+
* The palette as `--color-*` custom properties for `+html.tsx`: the light
|
|
497
|
+
* values on `:root`, the dark ones under the `prefers-color-scheme` media
|
|
498
|
+
* query, and both again keyed on `data-theme`, which `setColorScheme` (and
|
|
499
|
+
* the `getThemeBootScript` boot script) set on the root element to force a
|
|
500
|
+
* scheme regardless of the system's. `tint`/`onTint` are the same in both
|
|
501
|
+
* schemes, so the forced palettes leave them to the defaults and to
|
|
502
|
+
* `AccentProvider`.
|
|
503
|
+
*/
|
|
474
504
|
export function getThemeCSS(): string {
|
|
475
505
|
const format = (s: string) => s.replace(/[A-Z]/g, v => `-${v.toLowerCase()}`);
|
|
476
|
-
const render = (o: ColorValues) => Object.entries(o)
|
|
477
|
-
|
|
506
|
+
const render = (o: ColorValues, skipAccent = false) => Object.entries(o)
|
|
507
|
+
.filter(([k]) => !skipAccent || (k !== 'tint' && k !== 'onTint'))
|
|
508
|
+
.map(([k,v]) => `\t\t${`--color-${format(k)}`}: ${v};`).join('\n');
|
|
478
509
|
return `
|
|
479
510
|
:root {
|
|
480
511
|
color-scheme: light dark;
|
|
@@ -485,6 +516,14 @@ export function getThemeCSS(): string {
|
|
|
485
516
|
${render(colors.dark)}
|
|
486
517
|
}
|
|
487
518
|
}
|
|
519
|
+
:root[data-theme="light"] {
|
|
520
|
+
color-scheme: light;
|
|
521
|
+
${render(colors.light, true)}
|
|
522
|
+
}
|
|
523
|
+
:root[data-theme="dark"] {
|
|
524
|
+
color-scheme: dark;
|
|
525
|
+
${render(colors.dark, true)}
|
|
526
|
+
}
|
|
488
527
|
`;
|
|
489
528
|
}
|
|
490
529
|
|