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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/dist/native/Box/index.js +120 -0
  4. package/dist/native/Button/index.js +135 -0
  5. package/dist/native/Card/index.js +67 -0
  6. package/dist/native/Checkbox/index.js +112 -0
  7. package/dist/native/Entry/index.js +76 -0
  8. package/dist/native/HeaderBar/index.js +79 -0
  9. package/dist/native/Switch/index.js +83 -0
  10. package/dist/native/index.js +8 -0
  11. package/dist/native/theme.js +16 -0
  12. package/dist/native/tokens.css.js +77 -0
  13. package/dist/types/Box/index.d.ts +23 -0
  14. package/dist/types/Box/index.d.ts.map +1 -0
  15. package/dist/types/Button/index.d.ts +22 -0
  16. package/dist/types/Button/index.d.ts.map +1 -0
  17. package/dist/types/Card/index.d.ts +14 -0
  18. package/dist/types/Card/index.d.ts.map +1 -0
  19. package/dist/types/Checkbox/index.d.ts +13 -0
  20. package/dist/types/Checkbox/index.d.ts.map +1 -0
  21. package/dist/types/Entry/index.d.ts +17 -0
  22. package/dist/types/Entry/index.d.ts.map +1 -0
  23. package/dist/types/HeaderBar/index.d.ts +16 -0
  24. package/dist/types/HeaderBar/index.d.ts.map +1 -0
  25. package/dist/types/Switch/index.d.ts +13 -0
  26. package/dist/types/Switch/index.d.ts.map +1 -0
  27. package/dist/types/index.d.ts +17 -0
  28. package/dist/types/index.d.ts.map +1 -0
  29. package/dist/types/theme.d.ts +77 -0
  30. package/dist/types/theme.d.ts.map +1 -0
  31. package/dist/types/tokens.css.d.ts +76 -0
  32. package/dist/types/tokens.css.d.ts.map +1 -0
  33. package/dist/web/Box/index.js +150 -0
  34. package/dist/web/Box/styles.css +39 -0
  35. package/dist/web/Button/index.js +132 -0
  36. package/dist/web/Button/styles.css +81 -0
  37. package/dist/web/Card/index.js +75 -0
  38. package/dist/web/Card/styles.css +91 -0
  39. package/dist/web/Checkbox/index.js +116 -0
  40. package/dist/web/Checkbox/styles.css +99 -0
  41. package/dist/web/Entry/index.js +74 -0
  42. package/dist/web/Entry/styles.css +113 -0
  43. package/dist/web/HeaderBar/index.js +87 -0
  44. package/dist/web/HeaderBar/styles.css +117 -0
  45. package/dist/web/Switch/index.js +86 -0
  46. package/dist/web/Switch/styles.css +134 -0
  47. package/dist/web/_tokens/styles.css +32 -0
  48. package/dist/web/index.js +8 -0
  49. package/dist/web/theme.js +16 -0
  50. package/dist/web/tokens.css.js +79 -0
  51. package/package.json +52 -31
  52. package/dist/Button.d.mts +0 -5
  53. package/dist/Button.d.ts +0 -5
  54. package/dist/Button.js +0 -63
  55. package/dist/Button.mjs +0 -41
  56. package/dist/button.d.mts +0 -5
  57. package/dist/button.d.ts +0 -5
  58. package/dist/button.js +0 -63
  59. package/dist/button.mjs +0 -15
  60. package/dist/chunk-FWCSY2DS.mjs +0 -37
  61. package/dist/styles.css +0 -346
  62. package/dist/styles.d.mts +0 -2
  63. package/dist/styles.d.ts +0 -2
  64. package/dist/styles.js +0 -1
  65. 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
+ });