barua-ui 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/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # barua-ui
2
+
3
+ Barua UI as React components — an Apple-DNA design system (Barua blue, real
4
+ glass materials, HIG type) built on **pure CSS**, with thin typed React
5
+ bindings on top. The CSS is the source of truth; components just compose the
6
+ `.b-*` class system and add the interactive behaviors the React way.
7
+
8
+ ## What is in it
9
+
10
+ 278 components and hooks — every block in the system has one. The full
11
+ list, grouped, is at <https://ui.barua.tz/docs/react.html>, and every example in
12
+ the documentation carries a **React** tab with the JSX that produces it.
13
+
14
+ ```tsx
15
+ import { Card, CardBody, Stat, Button, Icon } from "barua-ui";
16
+
17
+ <Card compact>
18
+ <CardBody>
19
+ <Stat label="Outstanding" value="TZS 4.2M" />
20
+ <Button variant="primary">
21
+ <Icon name="check" /> Mark paid
22
+ </Button>
23
+ </CardBody>
24
+ </Card>
25
+ ```
26
+
27
+ Components render the documented markup and nothing else, so anything you read
28
+ on the docs pages applies unchanged. Each one forwards `className` and its ref,
29
+ so you are never boxed in.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ npm install barua-ui
35
+ ```
36
+
37
+ ## Use
38
+
39
+ ```tsx
40
+ // 1. Load the stylesheet once (any bundler handles the @imports)
41
+ import "barua-ui/css";
42
+
43
+ // 2. Mount the provider at your root
44
+ import { BaruaProvider, Button, Card, CardBody, useToast } from "barua-ui";
45
+
46
+ function Root() {
47
+ return (
48
+ <BaruaProvider theme="auto" accent="blue" glass={false}>
49
+ <App />
50
+ </BaruaProvider>
51
+ );
52
+ }
53
+
54
+ function App() {
55
+ const toast = useToast();
56
+ return (
57
+ <Card variant="glass" interactive>
58
+ <CardBody>
59
+ <Button
60
+ variant="primary"
61
+ size="lg"
62
+ onClick={() => toast({ title: "Karibu!", variant: "success" })}
63
+ >
64
+ Habari, Barua!
65
+ </Button>
66
+ </CardBody>
67
+ </Card>
68
+ );
69
+ }
70
+ ```
71
+
72
+ SSR/Next.js: render `<ThemeScript />` in `<head>` so the stored theme applies
73
+ before first paint.
74
+
75
+ ## What's in the box
76
+
77
+ - **Provider** — `BaruaProvider` (theme / `accent` re-tint / Liquid Glass
78
+ `glass` mode / Tier-2 `refraction`), `ThemeScript`, `useToast`
79
+ - **Actions** — `Button`, `ButtonLink`, `Fab`, `Toolbar`
80
+ - **Forms** — `Field`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`,
81
+ `Switch`, `Slider`, `OtpInput`
82
+ - **Content** — `Card` family (incl. `StoryCard`, flush iOS-Settings groups),
83
+ `List`/`ListItem`, `Badge`, `Tag`, `Chip`, `Avatar`(+`AvatarGroup`),
84
+ `IconTile`, `Stat`, `EmptyState`
85
+ - **Feedback** — `Alert`, `Progress`, `Spinner`, `Skeleton`, `StatusDot`,
86
+ toasts
87
+ - **Overlays** — `Modal`, `AlertDialog`, `Sheet`, `BottomSheet`, `Popover`
88
+ (arrowed/card), `Tooltip` — all on native `<dialog>`, centered by default
89
+ - **Navigation** — `Segmented`, `Tabs`/`TabPanel`, `Breadcrumbs`
90
+
91
+ Anything not wrapped yet is still one `className` away — the entire `.b-*`
92
+ system ships in `barua-ui/css`, documented in the Barua UI docs site.
93
+
94
+ ## Theming
95
+
96
+ ```tsx
97
+ <BaruaProvider theme="dark" accent="purple" glass refraction />
98
+ ```
99
+
100
+ - `theme`: `"auto" | "light" | "dark"` → `data-theme` on `<html>`
101
+ - `accent`: re-tints every component from one token
102
+ - `glass`: Liquid Glass mode — re-skins the whole system
103
+ - `refraction`: SVG-displacement refraction on liquid objects (Blink only,
104
+ graceful elsewhere)
105
+
106
+ Modern evergreen browsers: `light-dark()`, `@layer`, `color-mix()`, Popover
107
+ API, `<dialog>`.
package/css/barua.css ADDED
@@ -0,0 +1,28 @@
1
+ /* ==========================================================================
2
+ Barua UI — single entry point
3
+ <link rel="stylesheet" href="css/barua.css">
4
+
5
+ Layer order (later wins): reset < tokens < base < utilities < components.
6
+ Your overrides go in unlayered CSS, which always beats layered rules.
7
+ ========================================================================== */
8
+
9
+ @layer tokens, base, utilities, components;
10
+
11
+ @import url("tokens.css");
12
+ @import url("base.css");
13
+ @import url("utilities.css");
14
+ @import url("components/actions.css");
15
+ @import url("components/nav.css");
16
+ @import url("components/forms.css");
17
+ @import url("components/content.css");
18
+ @import url("components/feedback.css");
19
+ @import url("components/overlays.css");
20
+ @import url("components/charts.css");
21
+ @import url("components/media.css");
22
+ @import url("components/specialized.css");
23
+ @import url("components/productivity.css");
24
+ @import url("components/mobile.css");
25
+ @import url("components/auth.css");
26
+ @import url("components/control-center.css");
27
+ @import url("components/marketing.css");
28
+ @import url("liquid.css");
package/css/base.css ADDED
@@ -0,0 +1,242 @@
1
+ /* ==========================================================================
2
+ Barua UI — Base
3
+ Reset, document defaults, typography elements, focus, selection,
4
+ interaction-state conventions.
5
+ ========================================================================== */
6
+
7
+ @layer base {
8
+ *, *::before, *::after { box-sizing: border-box; }
9
+
10
+ * { margin: 0; }
11
+ /* The universal reset above would defeat the UA's centering of dialogs
12
+ and popovers (both center via `margin: auto` in the top layer). */
13
+ dialog, [popover] { margin: auto; }
14
+
15
+ html {
16
+ -webkit-text-size-adjust: 100%;
17
+ tab-size: 4;
18
+ }
19
+
20
+ body {
21
+ font-family: var(--b-font-sans);
22
+ font-size: var(--b-text-body);
23
+ line-height: var(--b-leading-body);
24
+ letter-spacing: var(--b-tracking-text);
25
+ color: var(--b-text);
26
+ background: var(--b-bg);
27
+ -webkit-font-smoothing: antialiased;
28
+ text-rendering: optimizeLegibility;
29
+ min-height: 100dvh;
30
+ }
31
+
32
+ img, picture, video, canvas, svg { display: block; max-width: 100%; }
33
+ input, button, textarea, select { font: inherit; color: inherit; letter-spacing: inherit; }
34
+ p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
35
+
36
+ /* The hidden attribute means hidden. A display utility on the same element
37
+ would otherwise win on specificity and quietly keep it on screen. */
38
+ [hidden] { display: none !important; }
39
+
40
+ /* ---- Typographic elements -------------------------------------------- */
41
+ h1, h2, h3, h4, h5, h6 {
42
+ font-weight: var(--b-weight-semibold);
43
+ line-height: var(--b-leading-tight);
44
+ letter-spacing: var(--b-tracking-tight);
45
+ text-wrap: balance;
46
+ }
47
+ h1 { font-size: var(--b-text-large-title); font-weight: var(--b-weight-bold); }
48
+ h2 { font-size: var(--b-text-title1); }
49
+ h3 { font-size: var(--b-text-title2); }
50
+ h4 { font-size: var(--b-text-title3); }
51
+ /* At headline size and below, tight tracking is wrong — these match the
52
+ .b-headline / .b-subheadline roles exactly rather than diverging. */
53
+ h5 { font-size: var(--b-text-headline); line-height: var(--b-leading-snug); letter-spacing: var(--b-tracking-text); }
54
+ h6 { font-size: var(--b-text-subheadline); text-transform: uppercase;
55
+ letter-spacing: var(--b-tracking-wide); color: var(--b-text-secondary); }
56
+
57
+ small { font-size: var(--b-text-footnote); color: var(--b-text-secondary); }
58
+ strong, b { font-weight: var(--b-weight-semibold); }
59
+
60
+ a {
61
+ color: var(--b-color-accent-text);
62
+ text-decoration: none;
63
+ }
64
+ a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
65
+
66
+ code, kbd, samp, pre { font-family: var(--b-font-mono); font-size: 0.875em; }
67
+ code {
68
+ background: var(--b-fill-tertiary);
69
+ border-radius: var(--b-radius-xs);
70
+ padding: 0.125em 0.375em;
71
+ }
72
+ pre {
73
+ overflow: auto;
74
+ padding: var(--b-space-4);
75
+ background: var(--b-surface-2);
76
+ border: 1px solid var(--b-separator);
77
+ border-radius: var(--b-radius-md);
78
+ line-height: var(--b-leading-snug);
79
+ }
80
+ pre code { background: none; padding: 0; border-radius: 0; font-size: var(--b-text-footnote); }
81
+
82
+ hr {
83
+ border: none;
84
+ border-top: 1px solid var(--b-separator);
85
+ margin: var(--b-space-6) 0;
86
+ }
87
+
88
+ blockquote {
89
+ border-inline-start: 3px solid var(--b-color-accent);
90
+ padding: var(--b-space-1) var(--b-space-4);
91
+ color: var(--b-text-secondary);
92
+ }
93
+
94
+ /* Lists in prose. The reset strips markers from every list so that navigation
95
+ and component lists are unadorned; these put them back where a list is
96
+ actually a list — rendered markdown, a card body, an article. Component
97
+ lists opt out with .b-list, which is unaffected. */
98
+ :where(ul, ol):not([class]) {
99
+ padding-inline-start: var(--b-space-6);
100
+ list-style-position: outside;
101
+ }
102
+ :where(ul):not([class]) { list-style-type: disc; }
103
+ :where(ol):not([class]) { list-style-type: decimal; }
104
+ :where(ul, ol):not([class]) :where(ul, ol) {
105
+ margin-block-start: var(--b-space-1);
106
+ }
107
+ :where(ul, ol):not([class]) :where(ul):not([class]) { list-style-type: circle; }
108
+ :where(ul, ol):not([class]) > li + li { margin-block-start: var(--b-space-1); }
109
+ :where(ul, ol):not([class]) > li::marker { color: var(--b-text-tertiary); }
110
+
111
+ ::selection {
112
+ background: color-mix(in srgb, var(--b-color-accent) 28%, transparent);
113
+ }
114
+
115
+ /* ---- Focus ring (Focus Ring component) -------------------------------- */
116
+ :focus-visible {
117
+ outline: none;
118
+ box-shadow: var(--b-focus-ring);
119
+ border-radius: var(--b-radius-xs);
120
+ }
121
+ .b-focus-ring:focus-visible,
122
+ button:focus-visible, [href]:focus-visible,
123
+ input:focus-visible, select:focus-visible, textarea:focus-visible,
124
+ [tabindex]:focus-visible {
125
+ outline: none;
126
+ box-shadow: var(--b-focus-ring);
127
+ }
128
+
129
+ /* ---- Interaction-state conventions ------------------------------------
130
+ Every interactive component honours:
131
+ :hover / .is-hover — hover affordance
132
+ :active / .is-pressed — pressed
133
+ .is-active — current / toggled-on
134
+ .is-selected — selection
135
+ .is-dragging — mid-drag
136
+ :disabled / .is-disabled — non-interactive
137
+ ------------------------------------------------------------------------- */
138
+ :disabled, .is-disabled {
139
+ opacity: var(--b-opacity-disabled);
140
+ cursor: not-allowed !important;
141
+ pointer-events: var(--b-disabled-pointer, auto);
142
+ }
143
+ .is-dragging { opacity: 0.55; cursor: grabbing !important; }
144
+
145
+ [draggable="true"] { cursor: grab; }
146
+
147
+ /* ---- Scrollbars (subtle, macOS-like) ---------------------------------- */
148
+ * {
149
+ scrollbar-width: thin;
150
+ scrollbar-color: var(--b-fill) transparent;
151
+ }
152
+ *::-webkit-scrollbar { width: 8px; height: 8px; }
153
+ *::-webkit-scrollbar-thumb {
154
+ background: var(--b-fill);
155
+ border-radius: var(--b-radius-full);
156
+ }
157
+ *::-webkit-scrollbar-track { background: transparent; }
158
+
159
+ /* ---- Reduced motion ---------------------------------------------------- */
160
+ @media (prefers-reduced-motion: reduce) {
161
+ *, *::before, *::after {
162
+ animation-duration: 0.01ms !important;
163
+ animation-iteration-count: 1 !important;
164
+ transition-duration: 0.01ms !important;
165
+ scroll-behavior: auto !important;
166
+ }
167
+ }
168
+
169
+ /* ---- Screen-reader-only ------------------------------------------------ */
170
+ .b-sr-only {
171
+ position: absolute;
172
+ width: 1px; height: 1px;
173
+ padding: 0; margin: -1px;
174
+ overflow: hidden;
175
+ clip-path: inset(50%);
176
+ white-space: nowrap;
177
+ border: 0;
178
+ }
179
+
180
+ /* Skip link — appears on focus */
181
+ .b-skip-link {
182
+ position: fixed;
183
+ inset-block-start: var(--b-space-2);
184
+ inset-inline-start: var(--b-space-2);
185
+ z-index: var(--b-z-tooltip);
186
+ padding: var(--b-space-2) var(--b-space-4);
187
+ background: var(--b-elevated);
188
+ border-radius: var(--b-radius-md);
189
+ box-shadow: var(--b-elevation-4);
190
+ font-weight: var(--b-weight-semibold);
191
+ translate: 0 calc(-100% - var(--b-space-4));
192
+ transition: translate var(--b-duration-fast) var(--b-ease-out);
193
+ }
194
+ .b-skip-link:focus-visible { translate: 0 0; }
195
+
196
+ /* ---- Keyboard shortcut (kbd) ------------------------------------------ */
197
+ kbd, .b-kbd {
198
+ display: inline-flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ min-width: 1.4em;
202
+ padding: 0.1em 0.45em;
203
+ font-family: var(--b-font-sans);
204
+ font-size: var(--b-text-caption);
205
+ font-weight: var(--b-weight-medium);
206
+ color: var(--b-text-secondary);
207
+ background: var(--b-fill-tertiary);
208
+ border: 1px solid var(--b-border);
209
+ border-bottom-width: 2px;
210
+ border-radius: var(--b-radius-sm);
211
+ }
212
+ }
213
+
214
+ /* UNLAYERED on purpose — must beat every layer. Closed dialogs and closed
215
+ popovers stay hidden even when a component class sets `display` (author
216
+ styles otherwise override the UA's hiding rules). `allow-discrete`
217
+ transitions on components still animate the exit before display flips. */
218
+ dialog:not([open]) { display: none; }
219
+ [popover]:not(:popover-open) { display: none; }
220
+
221
+ /* View Transitions (SwiftUI matchedGeometry energy) — OPT-IN: set
222
+ data-vt on <html> and same-origin navigations cross-fade with the
223
+ system's motion tokens. Reduced motion disables it automatically. */
224
+ @view-transition { navigation: auto; }
225
+ html:not([data-vt])::view-transition-old(root),
226
+ html:not([data-vt])::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
227
+ [data-vt]::view-transition-old(root) { animation-duration: 180ms; }
228
+ [data-vt]::view-transition-new(root) { animation-duration: 220ms; }
229
+ /* Matched elements: any two elements sharing a data-vt-name — a thumbnail here
230
+ and the hero it opens into there — are morphed by the browser instead of
231
+ cross-faded. The group carries the position and size change, so it wants the
232
+ system's spring rather than a linear tween. */
233
+ [data-vt]::view-transition-group(*) {
234
+ animation-duration: var(--b-duration-slow, 400ms);
235
+ animation-timing-function: var(--b-ease-spring, cubic-bezier(0.32, 0.72, 0, 1));
236
+ }
237
+ @media (prefers-reduced-motion: reduce) {
238
+ ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
239
+ ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
240
+ animation: none;
241
+ }
242
+ }