webadwaita 0.1.0 → 0.2.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 +21 -0
- package/README.md +112 -0
- package/dist/native/Box/index.js +120 -0
- package/dist/native/Button/index.js +135 -0
- package/dist/native/Card/index.js +67 -0
- package/dist/native/Checkbox/index.js +112 -0
- package/dist/native/Entry/index.js +76 -0
- package/dist/native/HeaderBar/index.js +79 -0
- package/dist/native/Switch/index.js +83 -0
- package/dist/native/index.js +8 -0
- package/dist/native/theme.js +16 -0
- package/dist/native/tokens.css.js +77 -0
- package/dist/types/Box/index.d.ts +23 -0
- package/dist/types/Box/index.d.ts.map +1 -0
- package/dist/types/Button/index.d.ts +22 -0
- package/dist/types/Button/index.d.ts.map +1 -0
- package/dist/types/Card/index.d.ts +14 -0
- package/dist/types/Card/index.d.ts.map +1 -0
- package/dist/types/Checkbox/index.d.ts +13 -0
- package/dist/types/Checkbox/index.d.ts.map +1 -0
- package/dist/types/Entry/index.d.ts +17 -0
- package/dist/types/Entry/index.d.ts.map +1 -0
- package/dist/types/HeaderBar/index.d.ts +16 -0
- package/dist/types/HeaderBar/index.d.ts.map +1 -0
- package/dist/types/Switch/index.d.ts +13 -0
- package/dist/types/Switch/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/theme.d.ts +77 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/dist/types/tokens.css.d.ts +76 -0
- package/dist/types/tokens.css.d.ts.map +1 -0
- package/dist/web/Box/index.js +150 -0
- package/dist/web/Box/styles.css +39 -0
- package/dist/web/Button/index.js +132 -0
- package/dist/web/Button/styles.css +81 -0
- package/dist/web/Card/index.js +75 -0
- package/dist/web/Card/styles.css +91 -0
- package/dist/web/Checkbox/index.js +116 -0
- package/dist/web/Checkbox/styles.css +99 -0
- package/dist/web/Entry/index.js +74 -0
- package/dist/web/Entry/styles.css +113 -0
- package/dist/web/HeaderBar/index.js +87 -0
- package/dist/web/HeaderBar/styles.css +117 -0
- package/dist/web/Switch/index.js +86 -0
- package/dist/web/Switch/styles.css +134 -0
- package/dist/web/_tokens/styles.css +32 -0
- package/dist/web/index.js +8 -0
- package/dist/web/theme.js +16 -0
- package/dist/web/tokens.css.js +79 -0
- package/package.json +52 -31
- package/dist/Button.d.mts +0 -5
- package/dist/Button.d.ts +0 -5
- package/dist/Button.js +0 -63
- package/dist/Button.mjs +0 -41
- package/dist/button.d.mts +0 -5
- package/dist/button.d.ts +0 -5
- package/dist/button.js +0 -63
- package/dist/button.mjs +0 -15
- package/dist/chunk-FWCSY2DS.mjs +0 -37
- package/dist/styles.css +0 -346
- package/dist/styles.d.mts +0 -2
- package/dist/styles.d.ts +0 -2
- package/dist/styles.js +0 -1
- package/dist/styles.mjs +0 -1
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,112 @@
|
|
|
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
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install webadwaita react-strict-dom
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Peer deps: `react ^19`, plus `react-dom` (web) or `react-native >= 0.79` (native).
|
|
28
|
+
|
|
29
|
+
## Setup
|
|
30
|
+
|
|
31
|
+
WebAdwaita ships precompiled JS plus per-component CSS — no special bundler config required. Install, import, render:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install webadwaita react-strict-dom
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { Button, Card } from 'webadwaita';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. Each component side-effect-imports its own CSS chunk, so your bundler ships only the styles for the components you actually use (works in Next.js, Vite, CRA, anywhere with a modern bundler honoring `package.json#sideEffects`).
|
|
42
|
+
|
|
43
|
+
**Native (Expo / Metro):** the package's `react-native` exports condition routes Metro to a precompiled native build that uses RN `StyleSheet` at runtime. No Babel preset to wire up in your app — just `import { Button } from 'webadwaita'`.
|
|
44
|
+
|
|
45
|
+
### How the build works
|
|
46
|
+
|
|
47
|
+
The library has a build step (`npm run build`) that produces:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
dist/
|
|
51
|
+
web/<Component>/index.js ← starts with `import './styles.css'`
|
|
52
|
+
web/<Component>/styles.css ← only that component's atomic rules
|
|
53
|
+
web/_tokens/styles.css ← shared baseline (loaded once)
|
|
54
|
+
native/<Component>/index.js ← RN StyleSheet output, no CSS
|
|
55
|
+
types/<Component>/index.d.ts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Two design-token notes carry over to consumer code:
|
|
59
|
+
|
|
60
|
+
- Tokens still live in [`src/tokens.css.ts`](./src/tokens.css.ts). The `.css.ts` filename is required: react-strict-dom's StyleX config treats `.css` as the token-file extension, so any file calling `css.defineConsts` / `css.defineVars` must follow that convention.
|
|
61
|
+
- [`src/theme.ts`](./src/theme.ts) re-exports the same tokens for runtime use.
|
|
62
|
+
|
|
63
|
+
## Use
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Box, Button, Card, Entry, Switch } from 'webadwaita';
|
|
67
|
+
|
|
68
|
+
export function Settings() {
|
|
69
|
+
const [name, setName] = useState('');
|
|
70
|
+
const [enabled, setEnabled] = useState(true);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Card title="Profile">
|
|
74
|
+
<Box gap="md">
|
|
75
|
+
<Entry placeholder="Your name" value={name} onChange={setName} />
|
|
76
|
+
<Box direction="row" align="center" gap="sm">
|
|
77
|
+
<Switch checked={enabled} onChange={setEnabled} />
|
|
78
|
+
<span>Enabled</span>
|
|
79
|
+
</Box>
|
|
80
|
+
<Button variant="suggested">Save</Button>
|
|
81
|
+
</Box>
|
|
82
|
+
</Card>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Develop
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
# library
|
|
91
|
+
npm install
|
|
92
|
+
npm run build # produces dist/{web,native,types}
|
|
93
|
+
npm run typecheck
|
|
94
|
+
|
|
95
|
+
# web Storybook (Vite-powered)
|
|
96
|
+
npm --prefix apps/storybook-web install
|
|
97
|
+
npm run storybook:web # opens http://localhost:6006
|
|
98
|
+
|
|
99
|
+
# native Storybook (Expo + @storybook/react-native v8, on-device UI)
|
|
100
|
+
npm --prefix apps/storybook-native install
|
|
101
|
+
npm run storybook:native # then press i / a / w in the Expo CLI
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
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:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
npm --prefix apps/storybook-native run storybook-generate
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT — see [LICENSE](./LICENSE). Inspired by `react-strict-dom`'s `apps/example-ui/` and the libadwaita design system.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { css, html } from 'react-strict-dom';
|
|
3
|
+
import { spacing } from '../tokens.css';
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Layout primitive — a flex container, the building block for everything else.
|
|
7
|
+
* Adwaita-inspired apps lean heavily on simple horizontal/vertical stacks.
|
|
8
|
+
*/
|
|
9
|
+
export function Box(props) {
|
|
10
|
+
const {
|
|
11
|
+
children,
|
|
12
|
+
direction = 'column',
|
|
13
|
+
gap,
|
|
14
|
+
padding,
|
|
15
|
+
align,
|
|
16
|
+
justify,
|
|
17
|
+
grow,
|
|
18
|
+
wrap,
|
|
19
|
+
style
|
|
20
|
+
} = props;
|
|
21
|
+
return /*#__PURE__*/_jsx(html.div, {
|
|
22
|
+
style: [styles.base, direction === 'row' ? styles.row : styles.column, wrap && styles.wrap, grow && styles.grow, align != null && alignStyles[align], justify != null && justifyStyles[justify], gap != null && gapStyles[gap], padding != null && paddingStyles[padding], style],
|
|
23
|
+
children: children
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const styles = css.create({
|
|
27
|
+
base: {
|
|
28
|
+
display: 'flex',
|
|
29
|
+
minWidth: 0
|
|
30
|
+
},
|
|
31
|
+
row: {
|
|
32
|
+
flexDirection: 'row'
|
|
33
|
+
},
|
|
34
|
+
column: {
|
|
35
|
+
flexDirection: 'column'
|
|
36
|
+
},
|
|
37
|
+
wrap: {
|
|
38
|
+
flexWrap: 'wrap'
|
|
39
|
+
},
|
|
40
|
+
grow: {
|
|
41
|
+
flexGrow: 1
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const alignStyles = css.create({
|
|
45
|
+
'flex-start': {
|
|
46
|
+
alignItems: 'flex-start'
|
|
47
|
+
},
|
|
48
|
+
center: {
|
|
49
|
+
alignItems: 'center'
|
|
50
|
+
},
|
|
51
|
+
'flex-end': {
|
|
52
|
+
alignItems: 'flex-end'
|
|
53
|
+
},
|
|
54
|
+
stretch: {
|
|
55
|
+
alignItems: 'stretch'
|
|
56
|
+
},
|
|
57
|
+
baseline: {
|
|
58
|
+
alignItems: 'baseline'
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const justifyStyles = css.create({
|
|
62
|
+
'flex-start': {
|
|
63
|
+
justifyContent: 'flex-start'
|
|
64
|
+
},
|
|
65
|
+
center: {
|
|
66
|
+
justifyContent: 'center'
|
|
67
|
+
},
|
|
68
|
+
'flex-end': {
|
|
69
|
+
justifyContent: 'flex-end'
|
|
70
|
+
},
|
|
71
|
+
'space-between': {
|
|
72
|
+
justifyContent: 'space-between'
|
|
73
|
+
},
|
|
74
|
+
'space-around': {
|
|
75
|
+
justifyContent: 'space-around'
|
|
76
|
+
},
|
|
77
|
+
'space-evenly': {
|
|
78
|
+
justifyContent: 'space-evenly'
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
const gapStyles = css.create({
|
|
82
|
+
xs: {
|
|
83
|
+
gap: spacing.xs
|
|
84
|
+
},
|
|
85
|
+
sm: {
|
|
86
|
+
gap: spacing.sm
|
|
87
|
+
},
|
|
88
|
+
md: {
|
|
89
|
+
gap: spacing.md
|
|
90
|
+
},
|
|
91
|
+
lg: {
|
|
92
|
+
gap: spacing.lg
|
|
93
|
+
},
|
|
94
|
+
xl: {
|
|
95
|
+
gap: spacing.xl
|
|
96
|
+
},
|
|
97
|
+
xxl: {
|
|
98
|
+
gap: spacing.xxl
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const paddingStyles = css.create({
|
|
102
|
+
xs: {
|
|
103
|
+
padding: spacing.xs
|
|
104
|
+
},
|
|
105
|
+
sm: {
|
|
106
|
+
padding: spacing.sm
|
|
107
|
+
},
|
|
108
|
+
md: {
|
|
109
|
+
padding: spacing.md
|
|
110
|
+
},
|
|
111
|
+
lg: {
|
|
112
|
+
padding: spacing.lg
|
|
113
|
+
},
|
|
114
|
+
xl: {
|
|
115
|
+
padding: spacing.xl
|
|
116
|
+
},
|
|
117
|
+
xxl: {
|
|
118
|
+
padding: spacing.xxl
|
|
119
|
+
}
|
|
120
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Adwaita-style button. Variants follow libadwaita conventions:
|
|
7
|
+
* - default: flat-ish neutral surface
|
|
8
|
+
* - suggested: blue accent (primary)
|
|
9
|
+
* - destructive: red, for irreversible actions
|
|
10
|
+
* - flat: no background, only on hover
|
|
11
|
+
* - pill: fully rounded (Adwaita "circular" buttons)
|
|
12
|
+
*/
|
|
13
|
+
export function Button(props) {
|
|
14
|
+
const {
|
|
15
|
+
children,
|
|
16
|
+
variant = 'default',
|
|
17
|
+
size = 'md',
|
|
18
|
+
disabled = false,
|
|
19
|
+
fullWidth = false,
|
|
20
|
+
onPress,
|
|
21
|
+
type = 'button'
|
|
22
|
+
} = props;
|
|
23
|
+
return /*#__PURE__*/_jsx(html.button, {
|
|
24
|
+
type: type,
|
|
25
|
+
disabled: disabled,
|
|
26
|
+
onClick: onPress,
|
|
27
|
+
style: [styles.base, sizeStyles[size], variantStyles[variant], fullWidth && styles.fullWidth, disabled && styles.disabled],
|
|
28
|
+
children: /*#__PURE__*/_jsx(html.span, {
|
|
29
|
+
style: [styles.label, labelSizeStyles[size]],
|
|
30
|
+
children: children
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const styles = css.create({
|
|
35
|
+
base: {
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignSelf: 'flex-start',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
flexDirection: 'row',
|
|
41
|
+
borderWidth: 0,
|
|
42
|
+
cursor: 'pointer',
|
|
43
|
+
transitionProperty: 'background-color, box-shadow, color',
|
|
44
|
+
transitionDuration: motion.durationFast,
|
|
45
|
+
transitionTimingFunction: motion.easing,
|
|
46
|
+
userSelect: 'none'
|
|
47
|
+
},
|
|
48
|
+
label: {
|
|
49
|
+
fontFamily: typography.fontFamily,
|
|
50
|
+
fontWeight: typography.weightStrong,
|
|
51
|
+
textAlign: 'center'
|
|
52
|
+
},
|
|
53
|
+
fullWidth: {
|
|
54
|
+
alignSelf: 'stretch'
|
|
55
|
+
},
|
|
56
|
+
disabled: {
|
|
57
|
+
opacity: 0.5,
|
|
58
|
+
cursor: 'not-allowed'
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const sizeStyles = css.create({
|
|
62
|
+
sm: {
|
|
63
|
+
paddingInline: spacing.md,
|
|
64
|
+
borderRadius: radius.sm,
|
|
65
|
+
height: 28
|
|
66
|
+
},
|
|
67
|
+
md: {
|
|
68
|
+
paddingInline: spacing.lg,
|
|
69
|
+
borderRadius: radius.md,
|
|
70
|
+
height: 36
|
|
71
|
+
},
|
|
72
|
+
lg: {
|
|
73
|
+
paddingInline: spacing.xl,
|
|
74
|
+
borderRadius: radius.md,
|
|
75
|
+
height: 44
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const labelSizeStyles = css.create({
|
|
79
|
+
sm: {
|
|
80
|
+
fontSize: typography.captionSize,
|
|
81
|
+
lineHeight: typography.captionLineHeight
|
|
82
|
+
},
|
|
83
|
+
md: {
|
|
84
|
+
fontSize: typography.bodySize,
|
|
85
|
+
lineHeight: typography.bodyLineHeight
|
|
86
|
+
},
|
|
87
|
+
lg: {
|
|
88
|
+
fontSize: typography.title4Size,
|
|
89
|
+
lineHeight: typography.title4LineHeight
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const variantStyles = css.create({
|
|
93
|
+
default: {
|
|
94
|
+
backgroundColor: {
|
|
95
|
+
default: palette.subtleBg,
|
|
96
|
+
':hover': palette.subtleBgHover,
|
|
97
|
+
':active': palette.subtleBgActive
|
|
98
|
+
},
|
|
99
|
+
color: palette.fg
|
|
100
|
+
},
|
|
101
|
+
suggested: {
|
|
102
|
+
backgroundColor: {
|
|
103
|
+
default: palette.accentBg,
|
|
104
|
+
':hover': palette.accentBgHover,
|
|
105
|
+
':active': palette.accentBgActive
|
|
106
|
+
},
|
|
107
|
+
color: palette.accentFg
|
|
108
|
+
},
|
|
109
|
+
destructive: {
|
|
110
|
+
backgroundColor: {
|
|
111
|
+
default: palette.destructiveBg,
|
|
112
|
+
':hover': palette.destructiveBgHover,
|
|
113
|
+
':active': palette.destructiveBgActive
|
|
114
|
+
},
|
|
115
|
+
color: palette.destructiveFg
|
|
116
|
+
},
|
|
117
|
+
flat: {
|
|
118
|
+
backgroundColor: {
|
|
119
|
+
default: 'transparent',
|
|
120
|
+
':hover': palette.subtleBg,
|
|
121
|
+
':active': palette.subtleBgHover
|
|
122
|
+
},
|
|
123
|
+
color: palette.fg
|
|
124
|
+
},
|
|
125
|
+
pill: {
|
|
126
|
+
backgroundColor: {
|
|
127
|
+
default: palette.subtleBg,
|
|
128
|
+
':hover': palette.subtleBgHover,
|
|
129
|
+
':active': palette.subtleBgActive
|
|
130
|
+
},
|
|
131
|
+
color: palette.fg,
|
|
132
|
+
borderRadius: radius.pill,
|
|
133
|
+
paddingInline: spacing.xl
|
|
134
|
+
}
|
|
135
|
+
});
|
|
@@ -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
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Adwaita-style card surface. Default has a soft shadow + rounded corners;
|
|
7
|
+
* "flat" drops the shadow for use inside other surfaces.
|
|
8
|
+
*/
|
|
9
|
+
export function Card(props) {
|
|
10
|
+
const {
|
|
11
|
+
children,
|
|
12
|
+
title,
|
|
13
|
+
variant = 'boxed',
|
|
14
|
+
padding = true
|
|
15
|
+
} = props;
|
|
16
|
+
return /*#__PURE__*/_jsxs(html.div, {
|
|
17
|
+
style: [styles.card, variant === 'boxed' ? styles.boxed : styles.flat],
|
|
18
|
+
children: [title != null && /*#__PURE__*/_jsx(html.div, {
|
|
19
|
+
style: styles.header,
|
|
20
|
+
children: /*#__PURE__*/_jsx(html.h3, {
|
|
21
|
+
style: styles.titleText,
|
|
22
|
+
children: title
|
|
23
|
+
})
|
|
24
|
+
}), /*#__PURE__*/_jsx(html.div, {
|
|
25
|
+
style: padding ? styles.body : null,
|
|
26
|
+
children: children
|
|
27
|
+
})]
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const styles = css.create({
|
|
31
|
+
card: {
|
|
32
|
+
backgroundColor: palette.cardBg,
|
|
33
|
+
borderRadius: radius.lg,
|
|
34
|
+
overflow: 'hidden',
|
|
35
|
+
display: 'flex',
|
|
36
|
+
flexDirection: 'column'
|
|
37
|
+
},
|
|
38
|
+
boxed: {
|
|
39
|
+
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04)',
|
|
40
|
+
borderWidth: 1,
|
|
41
|
+
borderStyle: 'solid',
|
|
42
|
+
borderColor: palette.border
|
|
43
|
+
},
|
|
44
|
+
flat: {
|
|
45
|
+
borderWidth: 1,
|
|
46
|
+
borderStyle: 'solid',
|
|
47
|
+
borderColor: palette.border
|
|
48
|
+
},
|
|
49
|
+
header: {
|
|
50
|
+
paddingBlock: spacing.md,
|
|
51
|
+
paddingInline: spacing.lg,
|
|
52
|
+
borderBottomWidth: 1,
|
|
53
|
+
borderBottomStyle: 'solid',
|
|
54
|
+
borderBottomColor: palette.border
|
|
55
|
+
},
|
|
56
|
+
titleText: {
|
|
57
|
+
margin: 0,
|
|
58
|
+
fontFamily: typography.fontFamily,
|
|
59
|
+
fontSize: typography.title4Size,
|
|
60
|
+
lineHeight: typography.title4LineHeight,
|
|
61
|
+
fontWeight: typography.weightBold,
|
|
62
|
+
color: palette.fg
|
|
63
|
+
},
|
|
64
|
+
body: {
|
|
65
|
+
padding: spacing.lg
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Adwaita-style checkbox. Solid blue square with a unicode check when on,
|
|
7
|
+
* subtle outlined square when off.
|
|
8
|
+
*/
|
|
9
|
+
export function Checkbox(props) {
|
|
10
|
+
const {
|
|
11
|
+
checked,
|
|
12
|
+
disabled = false,
|
|
13
|
+
onChange,
|
|
14
|
+
label
|
|
15
|
+
} = props;
|
|
16
|
+
const toggle = () => {
|
|
17
|
+
if (!disabled) onChange?.(!checked);
|
|
18
|
+
};
|
|
19
|
+
const box = /*#__PURE__*/_jsx(html.span, {
|
|
20
|
+
style: [styles.box, checked ? styles.boxOn : styles.boxOff],
|
|
21
|
+
children: checked && /*#__PURE__*/_jsx(html.span, {
|
|
22
|
+
style: styles.check,
|
|
23
|
+
children: '✓'
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
if (label == null) {
|
|
27
|
+
return /*#__PURE__*/_jsx(html.button, {
|
|
28
|
+
type: "button",
|
|
29
|
+
role: "checkbox",
|
|
30
|
+
"aria-checked": checked,
|
|
31
|
+
disabled: disabled,
|
|
32
|
+
onClick: toggle,
|
|
33
|
+
style: [styles.bareButton, disabled && styles.disabled],
|
|
34
|
+
children: box
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return /*#__PURE__*/_jsxs(html.button, {
|
|
38
|
+
type: "button",
|
|
39
|
+
role: "checkbox",
|
|
40
|
+
"aria-checked": checked,
|
|
41
|
+
disabled: disabled,
|
|
42
|
+
onClick: toggle,
|
|
43
|
+
style: [styles.row, disabled && styles.disabled],
|
|
44
|
+
children: [box, /*#__PURE__*/_jsx(html.span, {
|
|
45
|
+
style: styles.label,
|
|
46
|
+
children: label
|
|
47
|
+
})]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const SIZE = 18;
|
|
51
|
+
const styles = css.create({
|
|
52
|
+
bareButton: {
|
|
53
|
+
backgroundColor: 'transparent',
|
|
54
|
+
borderWidth: 0,
|
|
55
|
+
padding: 0,
|
|
56
|
+
cursor: 'pointer'
|
|
57
|
+
},
|
|
58
|
+
row: {
|
|
59
|
+
display: 'flex',
|
|
60
|
+
alignSelf: 'flex-start',
|
|
61
|
+
flexDirection: 'row',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
gap: spacing.sm,
|
|
64
|
+
backgroundColor: 'transparent',
|
|
65
|
+
borderWidth: 0,
|
|
66
|
+
padding: 0,
|
|
67
|
+
cursor: 'pointer'
|
|
68
|
+
},
|
|
69
|
+
box: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
width: SIZE,
|
|
72
|
+
height: SIZE,
|
|
73
|
+
borderRadius: radius.sm,
|
|
74
|
+
alignItems: 'center',
|
|
75
|
+
justifyContent: 'center',
|
|
76
|
+
transitionProperty: 'background-color, border-color',
|
|
77
|
+
transitionDuration: motion.durationFast,
|
|
78
|
+
transitionTimingFunction: motion.easing
|
|
79
|
+
},
|
|
80
|
+
boxOff: {
|
|
81
|
+
backgroundColor: 'transparent',
|
|
82
|
+
borderWidth: 1,
|
|
83
|
+
borderStyle: 'solid',
|
|
84
|
+
borderColor: {
|
|
85
|
+
default: palette.borderStrong,
|
|
86
|
+
':hover': palette.fg
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
boxOn: {
|
|
90
|
+
backgroundColor: {
|
|
91
|
+
default: palette.accentBg,
|
|
92
|
+
':hover': palette.accentBgHover
|
|
93
|
+
},
|
|
94
|
+
borderWidth: 0
|
|
95
|
+
},
|
|
96
|
+
check: {
|
|
97
|
+
color: palette.accentFg,
|
|
98
|
+
fontSize: 14,
|
|
99
|
+
lineHeight: 1,
|
|
100
|
+
fontWeight: 700
|
|
101
|
+
},
|
|
102
|
+
label: {
|
|
103
|
+
color: palette.fg,
|
|
104
|
+
fontFamily: typography.fontFamily,
|
|
105
|
+
fontSize: typography.bodySize,
|
|
106
|
+
lineHeight: typography.bodyLineHeight
|
|
107
|
+
},
|
|
108
|
+
disabled: {
|
|
109
|
+
opacity: 0.5,
|
|
110
|
+
cursor: 'not-allowed'
|
|
111
|
+
}
|
|
112
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
/**
|
|
6
|
+
* Adwaita-style text entry. Underline focus indicator + subtle filled surface,
|
|
7
|
+
* like libadwaita's `AdwEntry`.
|
|
8
|
+
*/
|
|
9
|
+
export function Entry(props) {
|
|
10
|
+
const {
|
|
11
|
+
value,
|
|
12
|
+
defaultValue,
|
|
13
|
+
placeholder,
|
|
14
|
+
disabled = false,
|
|
15
|
+
readOnly = false,
|
|
16
|
+
invalid = false,
|
|
17
|
+
type = 'text',
|
|
18
|
+
onChange,
|
|
19
|
+
onSubmit
|
|
20
|
+
} = props;
|
|
21
|
+
const latestValueRef = React.useRef(value ?? defaultValue ?? '');
|
|
22
|
+
return /*#__PURE__*/_jsx(html.input, {
|
|
23
|
+
type: type,
|
|
24
|
+
value: value,
|
|
25
|
+
defaultValue: defaultValue,
|
|
26
|
+
placeholder: placeholder,
|
|
27
|
+
disabled: disabled,
|
|
28
|
+
readOnly: readOnly,
|
|
29
|
+
onChange: e => {
|
|
30
|
+
latestValueRef.current = e.target.value;
|
|
31
|
+
onChange?.(e.target.value);
|
|
32
|
+
},
|
|
33
|
+
onKeyDown: e => {
|
|
34
|
+
if (e.key === 'Enter') onSubmit?.(latestValueRef.current);
|
|
35
|
+
},
|
|
36
|
+
style: [styles.input, invalid && styles.invalid, disabled && styles.disabled]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const styles = css.create({
|
|
40
|
+
input: {
|
|
41
|
+
backgroundColor: palette.subtleBg,
|
|
42
|
+
color: palette.fg,
|
|
43
|
+
borderWidth: 0,
|
|
44
|
+
borderBottomWidth: 2,
|
|
45
|
+
borderBottomStyle: 'solid',
|
|
46
|
+
borderBottomColor: {
|
|
47
|
+
default: 'transparent',
|
|
48
|
+
':hover': palette.borderStrong,
|
|
49
|
+
':focus': palette.accentBg
|
|
50
|
+
},
|
|
51
|
+
borderTopLeftRadius: radius.sm,
|
|
52
|
+
borderTopRightRadius: radius.sm,
|
|
53
|
+
borderBottomLeftRadius: 0,
|
|
54
|
+
borderBottomRightRadius: 0,
|
|
55
|
+
paddingBlock: spacing.sm,
|
|
56
|
+
paddingInline: spacing.md,
|
|
57
|
+
fontFamily: typography.fontFamily,
|
|
58
|
+
fontSize: typography.bodySize,
|
|
59
|
+
lineHeight: typography.bodyLineHeight,
|
|
60
|
+
minHeight: 36,
|
|
61
|
+
transitionProperty: 'border-color, background-color',
|
|
62
|
+
transitionDuration: motion.durationFast,
|
|
63
|
+
transitionTimingFunction: motion.easing
|
|
64
|
+
},
|
|
65
|
+
invalid: {
|
|
66
|
+
borderBottomColor: {
|
|
67
|
+
default: palette.destructiveBg,
|
|
68
|
+
':hover': palette.destructiveBg,
|
|
69
|
+
':focus': palette.destructiveBg
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
disabled: {
|
|
73
|
+
opacity: 0.5,
|
|
74
|
+
cursor: 'not-allowed'
|
|
75
|
+
}
|
|
76
|
+
});
|