webadwaita 0.1.0 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WebAdwaita contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # WebAdwaita
2
+
3
+ Cross-platform React component library inspired by GNOME's [libadwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/), built on [react-strict-dom](https://github.com/facebook/react-strict-dom). One TypeScript codebase renders on **web**, **iOS**, and **Android**.
4
+
5
+ ## Why react-strict-dom?
6
+
7
+ react-strict-dom is Meta's strict subset of `<html.*>` primitives plus a `css.create` API that compiles to **StyleX on web** and **React Native styles on native**. WebAdwaita uses it so every component is platform-neutral by default — no `.web.tsx` / `.native.tsx` forks needed.
8
+
9
+ ## Components
10
+
11
+ | Component | Purpose |
12
+ | --- | --- |
13
+ | `Box` | Flex layout primitive (direction / gap / padding / align / justify). |
14
+ | `Button` | Variants: `default`, `suggested`, `destructive`, `flat`, `pill`. Sizes: `sm` / `md` / `lg`. |
15
+ | `Entry` | Text input with focus underline (Adwaita `AdwEntry`). |
16
+ | `Switch` | Pill-shaped toggle with sliding thumb. |
17
+ | `Checkbox` | Square checkbox with optional label. |
18
+ | `Card` | Surface with optional title; `boxed` and `flat` variants. |
19
+ | `HeaderBar` | Top bar with leading / centered title / trailing slots. |
20
+
21
+ All design tokens live in [`src/tokens.css.ts`](./src/tokens.css.ts) — palette, radius, spacing, typography, motion. The `.css.ts` filename is required: react-strict-dom's Babel preset configures StyleX to treat `.css` as the token-file extension, so any file holding `css.defineConsts` / `css.defineVars` calls must be named `<name>.css.ts`.
22
+
23
+ [`src/theme.ts`](./src/theme.ts) re-exports the same tokens for runtime use (e.g. computed inline styles in app code).
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ npm install webadwaita react-strict-dom
29
+ ```
30
+
31
+ Peer deps: `react ^19`, plus `react-dom` (web) or `react-native >= 0.79` (native).
32
+
33
+ ## Setup
34
+
35
+ **WebAdwaita ships TypeScript source — not a pre-built bundle.** This is the same pattern Meta's `apps/example-ui` uses, and it's deliberate: the StyleX class names baked into a pre-built bundle would not match the CSS rules extracted by the consumer's PostCSS pipeline. Shipping source lets a single Babel pass at consumer build time produce both the class names *and* the matching CSS.
36
+
37
+ That means your app's bundler must run the `react-strict-dom` Babel preset over `node_modules/webadwaita/src` and your PostCSS config must scan the same files for CSS extraction.
38
+
39
+ **Vite (web):** see [playground/vite.config.ts](./playground/vite.config.ts) and [playground/postcss.config.cjs](./playground/postcss.config.cjs) for the working setup. The key bits:
40
+
41
+ ```ts
42
+ // vite.config.ts
43
+ plugins: [
44
+ react({
45
+ babel: { configFile: true },
46
+ include: [/\.[jt]sx?$/, '<...>/node_modules/webadwaita/src/**']
47
+ }),
48
+ babel({ filter: /\.[jt]sx?$/ })
49
+ ]
50
+ ```
51
+
52
+ ```js
53
+ // postcss.config.cjs
54
+ 'react-strict-dom/postcss-plugin': {
55
+ include: [
56
+ 'src/**/*.{ts,tsx}',
57
+ 'node_modules/webadwaita/src/**/*.{ts,tsx}'
58
+ ],
59
+ babelConfig
60
+ }
61
+ ```
62
+
63
+ Then add a `strict.css` file with the directive `@react-strict-dom;` and import it from your app's entry point — PostCSS replaces the directive with the generated CSS.
64
+
65
+ **Expo / Metro (native):** Metro picks up the source automatically. Add the `react-strict-dom/babel-preset` to your `babel.config.js` exactly like the [react-strict-dom expo-app example](https://github.com/facebook/react-strict-dom/blob/main/apps/expo-app/babel.config.js).
66
+
67
+ ## Use
68
+
69
+ ```tsx
70
+ import { Box, Button, Card, Entry, Switch } from 'webadwaita';
71
+
72
+ export function Settings() {
73
+ const [name, setName] = useState('');
74
+ const [enabled, setEnabled] = useState(true);
75
+
76
+ return (
77
+ <Card title="Profile">
78
+ <Box gap="md">
79
+ <Entry placeholder="Your name" value={name} onChange={setName} />
80
+ <Box direction="row" align="center" gap="sm">
81
+ <Switch checked={enabled} onChange={setEnabled} />
82
+ <span>Enabled</span>
83
+ </Box>
84
+ <Button variant="suggested">Save</Button>
85
+ </Box>
86
+ </Card>
87
+ );
88
+ }
89
+ ```
90
+
91
+ ## Develop
92
+
93
+ There is no library build step — consumers transform the TypeScript source directly. `npm run typecheck` is the only "build-like" command for the lib itself.
94
+
95
+ Two demo apps live under `apps/`:
96
+
97
+ ```sh
98
+ # library
99
+ npm install
100
+ npm run typecheck
101
+
102
+ # web Storybook (Vite-powered)
103
+ npm --prefix apps/storybook-web install
104
+ npm run storybook:web # opens http://localhost:6006
105
+
106
+ # native Storybook (Expo + @storybook/react-native v8, on-device UI)
107
+ npm --prefix apps/storybook-native install
108
+ npm run storybook:native # then press i / a / w in the Expo CLI
109
+ ```
110
+
111
+ The native app embeds the Storybook UI directly — open the Expo Go app on a phone (or a simulator) and pick a component from the on-device sidebar. After adding/removing `.stories.tsx` files, regenerate the registry:
112
+
113
+ ```sh
114
+ npm --prefix apps/storybook-native run storybook-generate
115
+ ```
116
+
117
+ ## License
118
+
119
+ MIT — see [LICENSE](./LICENSE). Inspired by `react-strict-dom`'s `apps/example-ui/` and the libadwaita design system.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared babel config used by Vite (web library build) and consumers
3
+ * who want to bundle WebAdwaita source through the react-strict-dom
4
+ * preset (e.g. Metro / Vite / Next.js postcss pipelines).
5
+ */
6
+ const dev = process.env.NODE_ENV !== 'production';
7
+
8
+ module.exports = {
9
+ parserOpts: {
10
+ plugins: ['typescript', 'jsx']
11
+ },
12
+ presets: [
13
+ [
14
+ 'react-strict-dom/babel-preset',
15
+ {
16
+ debug: dev,
17
+ dev,
18
+ rootDir: process.cwd(),
19
+ platform: process.env.WEBADWAITA_PLATFORM || 'web'
20
+ }
21
+ ]
22
+ ]
23
+ };
package/package.json CHANGED
@@ -1,43 +1,45 @@
1
1
  {
2
2
  "name": "webadwaita",
3
- "version": "0.1.0",
4
- "sideEffects": ["**/*.css"],
3
+ "version": "0.2.0",
4
+ "description": "Cross-platform Adwaita-styled UI components built on react-strict-dom.",
5
5
  "license": "MIT",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "src",
9
+ "babel.config.cjs",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "main": "./src/index.ts",
6
14
  "exports": {
7
- "./button": {
8
- "types": "./dist/button.d.ts",
9
- "import": "./dist/button.mjs",
10
- "require": "./dist/button.js"
11
- },
12
- "./styles": {
13
- "import": "./dist/styles.mjs",
14
- "require": "./dist/styles.js"
15
- },
16
- "./styles.css": "./dist/styles.css"
15
+ ".": "./src/index.ts",
16
+ "./babel-config": "./babel.config.cjs",
17
+ "./package.json": "./package.json"
17
18
  },
18
19
  "scripts": {
19
- "build": "tsup",
20
- "dev": "tsup --watch",
21
- "lint": "eslint . --max-warnings 0",
22
- "clean": "rm -rf .turbo node_modules dist"
20
+ "typecheck": "tsc -p tsconfig.json --noEmit",
21
+ "storybook:web": "npm --prefix apps/storybook-web run storybook",
22
+ "storybook:native": "npm --prefix apps/storybook-native run start"
23
23
  },
24
- "devDependencies": {
25
- "@repo/eslint-config": "*",
26
- "@repo/typescript-config": "*",
27
- "@storybook/react": "^8.2.6",
28
- "@tailwindcss/postcss": "^4.2.2",
29
- "@types/react": "^18.2.61",
30
- "@types/react-dom": "^18.2.19",
31
- "eslint": "^8.57.0",
32
- "tsup": "^8.0.2",
33
- "typescript": "5.5.4"
24
+ "peerDependencies": {
25
+ "react": "^19.0.0",
26
+ "react-dom": "^19.0.0",
27
+ "react-native": ">=0.79.0",
28
+ "react-strict-dom": ">=0.0.50"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "react-dom": { "optional": true },
32
+ "react-native": { "optional": true }
34
33
  },
35
- "dependencies": {
36
- "react": "^18.2.0",
37
- "tailwindcss": "^4.2.2"
34
+ "devDependencies": {
35
+ "@types/react": "^19.2.5",
36
+ "@types/react-dom": "^19.2.3",
37
+ "react": "^19.2.0",
38
+ "react-dom": "^19.2.0",
39
+ "react-strict-dom": "^0.0.55",
40
+ "typescript": "~5.9.3"
38
41
  },
39
- "files": ["dist"],
40
- "publishConfig": {
41
- "access": "public"
42
+ "engines": {
43
+ "node": ">=20.11.0"
42
44
  }
43
45
  }
@@ -0,0 +1,105 @@
1
+ import * as React from 'react';
2
+ import { css, html } from 'react-strict-dom';
3
+ import { spacing } from '../tokens.css';
4
+
5
+ type SpacingKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
6
+ type Direction = 'row' | 'column';
7
+ type Align = 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline';
8
+ type Justify =
9
+ | 'flex-start'
10
+ | 'center'
11
+ | 'flex-end'
12
+ | 'space-between'
13
+ | 'space-around'
14
+ | 'space-evenly';
15
+
16
+ export type BoxProps = {
17
+ children?: React.ReactNode;
18
+ direction?: Direction;
19
+ gap?: SpacingKey;
20
+ padding?: SpacingKey;
21
+ align?: Align;
22
+ justify?: Justify;
23
+ grow?: boolean;
24
+ wrap?: boolean;
25
+ style?: any;
26
+ };
27
+
28
+ /**
29
+ * Layout primitive — a flex container, the building block for everything else.
30
+ * Adwaita-inspired apps lean heavily on simple horizontal/vertical stacks.
31
+ */
32
+ export function Box(props: BoxProps) {
33
+ const {
34
+ children,
35
+ direction = 'column',
36
+ gap,
37
+ padding,
38
+ align,
39
+ justify,
40
+ grow,
41
+ wrap,
42
+ style
43
+ } = props;
44
+
45
+ return (
46
+ <html.div
47
+ style={[
48
+ styles.base,
49
+ direction === 'row' ? styles.row : styles.column,
50
+ wrap && styles.wrap,
51
+ grow && styles.grow,
52
+ align != null && alignStyles[align],
53
+ justify != null && justifyStyles[justify],
54
+ gap != null && gapStyles[gap],
55
+ padding != null && paddingStyles[padding],
56
+ style
57
+ ]}
58
+ >
59
+ {children}
60
+ </html.div>
61
+ );
62
+ }
63
+
64
+ const styles = css.create({
65
+ base: { display: 'flex', minWidth: 0 },
66
+ row: { flexDirection: 'row' },
67
+ column: { flexDirection: 'column' },
68
+ wrap: { flexWrap: 'wrap' },
69
+ grow: { flexGrow: 1 }
70
+ });
71
+
72
+ const alignStyles = css.create({
73
+ 'flex-start': { alignItems: 'flex-start' },
74
+ center: { alignItems: 'center' },
75
+ 'flex-end': { alignItems: 'flex-end' },
76
+ stretch: { alignItems: 'stretch' },
77
+ baseline: { alignItems: 'baseline' }
78
+ });
79
+
80
+ const justifyStyles = css.create({
81
+ 'flex-start': { justifyContent: 'flex-start' },
82
+ center: { justifyContent: 'center' },
83
+ 'flex-end': { justifyContent: 'flex-end' },
84
+ 'space-between': { justifyContent: 'space-between' },
85
+ 'space-around': { justifyContent: 'space-around' },
86
+ 'space-evenly': { justifyContent: 'space-evenly' }
87
+ });
88
+
89
+ const gapStyles = css.create({
90
+ xs: { gap: spacing.xs },
91
+ sm: { gap: spacing.sm },
92
+ md: { gap: spacing.md },
93
+ lg: { gap: spacing.lg },
94
+ xl: { gap: spacing.xl },
95
+ xxl: { gap: spacing.xxl }
96
+ });
97
+
98
+ const paddingStyles = css.create({
99
+ xs: { padding: spacing.xs },
100
+ sm: { padding: spacing.sm },
101
+ md: { padding: spacing.md },
102
+ lg: { padding: spacing.lg },
103
+ xl: { padding: spacing.xl },
104
+ xxl: { padding: spacing.xxl }
105
+ });
@@ -0,0 +1,153 @@
1
+ import * as React from 'react';
2
+ import { css, html } from 'react-strict-dom';
3
+ import { palette, radius, spacing, typography, motion } from '../tokens.css';
4
+
5
+ export type ButtonVariant =
6
+ | 'default'
7
+ | 'suggested'
8
+ | 'destructive'
9
+ | 'flat'
10
+ | 'pill';
11
+
12
+ export type ButtonSize = 'sm' | 'md' | 'lg';
13
+
14
+ export type ButtonProps = {
15
+ children?: React.ReactNode;
16
+ variant?: ButtonVariant;
17
+ size?: ButtonSize;
18
+ disabled?: boolean;
19
+ fullWidth?: boolean;
20
+ onPress?: () => void;
21
+ type?: 'button' | 'submit';
22
+ };
23
+
24
+ /**
25
+ * Adwaita-style button. Variants follow libadwaita conventions:
26
+ * - default: flat-ish neutral surface
27
+ * - suggested: blue accent (primary)
28
+ * - destructive: red, for irreversible actions
29
+ * - flat: no background, only on hover
30
+ * - pill: fully rounded (Adwaita "circular" buttons)
31
+ */
32
+ export function Button(props: ButtonProps) {
33
+ const {
34
+ children,
35
+ variant = 'default',
36
+ size = 'md',
37
+ disabled = false,
38
+ fullWidth = false,
39
+ onPress,
40
+ type = 'button'
41
+ } = props;
42
+
43
+ return (
44
+ <html.button
45
+ type={type}
46
+ disabled={disabled}
47
+ onClick={onPress}
48
+ style={[
49
+ styles.base,
50
+ sizeStyles[size],
51
+ variantStyles[variant],
52
+ fullWidth && styles.fullWidth,
53
+ disabled && styles.disabled
54
+ ]}
55
+ >
56
+ <html.span style={[styles.label, labelSizeStyles[size]]}>
57
+ {children}
58
+ </html.span>
59
+ </html.button>
60
+ );
61
+ }
62
+
63
+ const styles = css.create({
64
+ base: {
65
+ display: 'flex',
66
+ alignSelf: 'flex-start',
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ flexDirection: 'row',
70
+ borderWidth: 0,
71
+ cursor: 'pointer',
72
+ transitionProperty: 'background-color, box-shadow, color',
73
+ transitionDuration: motion.durationFast,
74
+ transitionTimingFunction: motion.easing,
75
+ userSelect: 'none'
76
+ },
77
+ label: {
78
+ fontFamily: typography.fontFamily,
79
+ fontWeight: typography.weightStrong,
80
+ textAlign: 'center'
81
+ },
82
+ fullWidth: { alignSelf: 'stretch' },
83
+ disabled: { opacity: 0.5, cursor: 'not-allowed' }
84
+ });
85
+
86
+ const sizeStyles = css.create({
87
+ sm: {
88
+ paddingInline: spacing.md,
89
+ borderRadius: radius.sm,
90
+ height: 28
91
+ },
92
+ md: {
93
+ paddingInline: spacing.lg,
94
+ borderRadius: radius.md,
95
+ height: 36
96
+ },
97
+ lg: {
98
+ paddingInline: spacing.xl,
99
+ borderRadius: radius.md,
100
+ height: 44
101
+ }
102
+ });
103
+
104
+ const labelSizeStyles = css.create({
105
+ sm: { fontSize: typography.captionSize, lineHeight: typography.captionLineHeight },
106
+ md: { fontSize: typography.bodySize, lineHeight: typography.bodyLineHeight },
107
+ lg: { fontSize: typography.title4Size, lineHeight: typography.title4LineHeight }
108
+ });
109
+
110
+ const variantStyles = css.create({
111
+ default: {
112
+ backgroundColor: {
113
+ default: palette.subtleBg,
114
+ ':hover': palette.subtleBgHover,
115
+ ':active': palette.subtleBgActive
116
+ },
117
+ color: palette.fg
118
+ },
119
+ suggested: {
120
+ backgroundColor: {
121
+ default: palette.accentBg,
122
+ ':hover': palette.accentBgHover,
123
+ ':active': palette.accentBgActive
124
+ },
125
+ color: palette.accentFg
126
+ },
127
+ destructive: {
128
+ backgroundColor: {
129
+ default: palette.destructiveBg,
130
+ ':hover': palette.destructiveBgHover,
131
+ ':active': palette.destructiveBgActive
132
+ },
133
+ color: palette.destructiveFg
134
+ },
135
+ flat: {
136
+ backgroundColor: {
137
+ default: 'transparent',
138
+ ':hover': palette.subtleBg,
139
+ ':active': palette.subtleBgHover
140
+ },
141
+ color: palette.fg
142
+ },
143
+ pill: {
144
+ backgroundColor: {
145
+ default: palette.subtleBg,
146
+ ':hover': palette.subtleBgHover,
147
+ ':active': palette.subtleBgActive
148
+ },
149
+ color: palette.fg,
150
+ borderRadius: radius.pill,
151
+ paddingInline: spacing.xl
152
+ }
153
+ });
@@ -0,0 +1,67 @@
1
+ import * as React from 'react';
2
+ import { css, html } from 'react-strict-dom';
3
+ import { palette, radius, spacing, typography } from '../tokens.css';
4
+
5
+ export type CardProps = {
6
+ children?: React.ReactNode;
7
+ title?: React.ReactNode;
8
+ /** "boxed" matches AdwBoxedList — solid surface w/ visible border. */
9
+ variant?: 'boxed' | 'flat';
10
+ padding?: boolean;
11
+ };
12
+
13
+ /**
14
+ * Adwaita-style card surface. Default has a soft shadow + rounded corners;
15
+ * "flat" drops the shadow for use inside other surfaces.
16
+ */
17
+ export function Card(props: CardProps) {
18
+ const { children, title, variant = 'boxed', padding = true } = props;
19
+
20
+ return (
21
+ <html.div style={[styles.card, variant === 'boxed' ? styles.boxed : styles.flat]}>
22
+ {title != null && (
23
+ <html.div style={styles.header}>
24
+ <html.h3 style={styles.titleText}>{title}</html.h3>
25
+ </html.div>
26
+ )}
27
+ <html.div style={padding ? styles.body : null}>{children}</html.div>
28
+ </html.div>
29
+ );
30
+ }
31
+
32
+ const styles = css.create({
33
+ card: {
34
+ backgroundColor: palette.cardBg,
35
+ borderRadius: radius.lg,
36
+ overflow: 'hidden',
37
+ display: 'flex',
38
+ flexDirection: 'column'
39
+ },
40
+ boxed: {
41
+ boxShadow: '0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04)',
42
+ borderWidth: 1,
43
+ borderStyle: 'solid',
44
+ borderColor: palette.border
45
+ },
46
+ flat: {
47
+ borderWidth: 1,
48
+ borderStyle: 'solid',
49
+ borderColor: palette.border
50
+ },
51
+ header: {
52
+ paddingBlock: spacing.md,
53
+ paddingInline: spacing.lg,
54
+ borderBottomWidth: 1,
55
+ borderBottomStyle: 'solid',
56
+ borderBottomColor: palette.border
57
+ },
58
+ titleText: {
59
+ margin: 0,
60
+ fontFamily: typography.fontFamily,
61
+ fontSize: typography.title4Size,
62
+ lineHeight: typography.title4LineHeight,
63
+ fontWeight: typography.weightBold,
64
+ color: palette.fg
65
+ },
66
+ body: { padding: spacing.lg }
67
+ });
@@ -0,0 +1,119 @@
1
+ import * as React from 'react';
2
+ import { css, html } from 'react-strict-dom';
3
+ import { palette, radius, motion, typography, spacing } from '../tokens.css';
4
+
5
+ export type CheckboxProps = {
6
+ checked: boolean;
7
+ disabled?: boolean;
8
+ onChange?: (checked: boolean) => void;
9
+ label?: React.ReactNode;
10
+ };
11
+
12
+ /**
13
+ * Adwaita-style checkbox. Solid blue square with a unicode check when on,
14
+ * subtle outlined square when off.
15
+ */
16
+ export function Checkbox(props: CheckboxProps) {
17
+ const { checked, disabled = false, onChange, label } = props;
18
+
19
+ const toggle = () => {
20
+ if (!disabled) onChange?.(!checked);
21
+ };
22
+
23
+ const box = (
24
+ <html.span style={[styles.box, checked ? styles.boxOn : styles.boxOff]}>
25
+ {checked && <html.span style={styles.check}>{'✓'}</html.span>}
26
+ </html.span>
27
+ );
28
+
29
+ if (label == null) {
30
+ return (
31
+ <html.button
32
+ type="button"
33
+ role="checkbox"
34
+ aria-checked={checked}
35
+ disabled={disabled}
36
+ onClick={toggle}
37
+ style={[styles.bareButton, disabled && styles.disabled]}
38
+ >
39
+ {box}
40
+ </html.button>
41
+ );
42
+ }
43
+
44
+ return (
45
+ <html.button
46
+ type="button"
47
+ role="checkbox"
48
+ aria-checked={checked}
49
+ disabled={disabled}
50
+ onClick={toggle}
51
+ style={[styles.row, disabled && styles.disabled]}
52
+ >
53
+ {box}
54
+ <html.span style={styles.label}>{label}</html.span>
55
+ </html.button>
56
+ );
57
+ }
58
+
59
+ const SIZE = 18;
60
+
61
+ const styles = css.create({
62
+ bareButton: {
63
+ backgroundColor: 'transparent',
64
+ borderWidth: 0,
65
+ padding: 0,
66
+ cursor: 'pointer'
67
+ },
68
+ row: {
69
+ display: 'flex',
70
+ alignSelf: 'flex-start',
71
+ flexDirection: 'row',
72
+ alignItems: 'center',
73
+ gap: spacing.sm,
74
+ backgroundColor: 'transparent',
75
+ borderWidth: 0,
76
+ padding: 0,
77
+ cursor: 'pointer'
78
+ },
79
+ box: {
80
+ display: 'flex',
81
+ width: SIZE,
82
+ height: SIZE,
83
+ borderRadius: radius.sm,
84
+ alignItems: 'center',
85
+ justifyContent: 'center',
86
+ transitionProperty: 'background-color, border-color',
87
+ transitionDuration: motion.durationFast,
88
+ transitionTimingFunction: motion.easing
89
+ },
90
+ boxOff: {
91
+ backgroundColor: 'transparent',
92
+ borderWidth: 1,
93
+ borderStyle: 'solid',
94
+ borderColor: {
95
+ default: palette.borderStrong,
96
+ ':hover': palette.fg
97
+ }
98
+ },
99
+ boxOn: {
100
+ backgroundColor: {
101
+ default: palette.accentBg,
102
+ ':hover': palette.accentBgHover
103
+ },
104
+ borderWidth: 0
105
+ },
106
+ check: {
107
+ color: palette.accentFg,
108
+ fontSize: 14,
109
+ lineHeight: 1,
110
+ fontWeight: 700
111
+ },
112
+ label: {
113
+ color: palette.fg,
114
+ fontFamily: typography.fontFamily,
115
+ fontSize: typography.bodySize,
116
+ lineHeight: typography.bodyLineHeight
117
+ },
118
+ disabled: { opacity: 0.5, cursor: 'not-allowed' }
119
+ });