sangam-ui 0.1.5
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 +603 -0
- package/assets/ESDS logo.png +0 -0
- package/dist/index.d.ts +1434 -0
- package/dist/index.js +2890 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img
|
|
4
|
+
src="https://unpkg.com/sangam-ui@latest/assets/ESDS%20logo.png"
|
|
5
|
+
alt="ESDS Sangam Design System"
|
|
6
|
+
width="160"
|
|
7
|
+
/>
|
|
8
|
+
|
|
9
|
+
<h1>sangam-ui</h1>
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
The **ESDS Sangam Design System** is the unified UI library for the ESDS platform. This package, `sangam-ui`, provides typed, accessible React components built on top of shared icons, design tokens, and global styles, so product teams can ship consistent and high‑quality interfaces quickly.
|
|
14
|
+
|
|
15
|
+
This README is written for developers who will install and use the design system in their applications.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Package Overview
|
|
20
|
+
|
|
21
|
+
`sangam-ui` is the **UI components package** of the Sangam Design System. It exposes reusable primitives, components, patterns, hooks, and types that are:
|
|
22
|
+
|
|
23
|
+
- **Themed** using Sangam tokens and styles.
|
|
24
|
+
- **Accessible** by default (ARIA-friendly, keyboard navigable).
|
|
25
|
+
- **Composable** and **type-safe** for modern React apps.
|
|
26
|
+
|
|
27
|
+
The design system is split into multiple npm packages so you can adopt only what you need while sharing a single source of truth for the visual language.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. Packages Included
|
|
32
|
+
|
|
33
|
+
| Layer | Package name | Description |
|
|
34
|
+
| ------ | --------------------- | -------------------------------------------------------------------------- |
|
|
35
|
+
| Icons | `@esds-sangam/icons` | SVG-based icon components and icon utilities for React. |
|
|
36
|
+
| Tokens | `@esds-sangam/tokens` | Design tokens for colors, spacing, typography, radius, shadows, and more. |
|
|
37
|
+
| Styles | `@esds-sangam/styles` | Global CSS, theme variables (light/dark), and Tailwind integration styles. |
|
|
38
|
+
| UI | `sangam-ui` | React UI primitives and components built on top of tokens and styles. |
|
|
39
|
+
|
|
40
|
+
You are currently viewing the README for the **UI package**: `sangam-ui`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 3. Installation
|
|
45
|
+
|
|
46
|
+
Install the full stack (recommended for most app teams):
|
|
47
|
+
|
|
48
|
+
#### npm
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install sangam-ui
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### yarn
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
yarn add sangam-ui
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### pnpm
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm add sangam-ui
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can also install packages individually if you only need a subset, for example:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install @esds-sangam/icons
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Peer Dependencies
|
|
75
|
+
|
|
76
|
+
The design system targets modern React applications.
|
|
77
|
+
|
|
78
|
+
| Package | Version (minimum) |
|
|
79
|
+
| ----------- | ----------------- |
|
|
80
|
+
| `react` | `^18.0.0` |
|
|
81
|
+
| `react-dom` | `^18.0.0` |
|
|
82
|
+
|
|
83
|
+
Ensure your application satisfies these peer dependencies to avoid warnings or runtime issues.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 5. Initial Setup (Vite + React + TypeScript)
|
|
88
|
+
|
|
89
|
+
Follow these steps after **React + TypeSCript** Project Setup to wire `sangam-ui` into project.
|
|
90
|
+
|
|
91
|
+
### 5.1 Install Tailwind CSS and PostCSS
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx tailwindcss init -p
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 5.3 Configure PostCSS — order matters
|
|
98
|
+
|
|
99
|
+
Create or update `postcss.config.cjs`. The `postcss-import` plugin **must come first** so `@import` rules are inlined before Tailwind processes the CSS stream:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// postcss.config.cjs
|
|
103
|
+
module.exports = {
|
|
104
|
+
plugins: {
|
|
105
|
+
"postcss-import": {}, // inline @import before Tailwind sees the file
|
|
106
|
+
tailwindcss: {},
|
|
107
|
+
autoprefixer: {},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 5.4 Configure Tailwind — use the Sangam preset
|
|
113
|
+
|
|
114
|
+
Update `tailwind.config.cjs` to spread the Sangam preset and include component class paths:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
// tailwind.config.cjs
|
|
118
|
+
const sangamTailwind = require("@esds-sangam/tailwind-config");
|
|
119
|
+
|
|
120
|
+
/** @type {import('tailwindcss').Config} */
|
|
121
|
+
module.exports = {
|
|
122
|
+
...sangamTailwind,
|
|
123
|
+
content: [
|
|
124
|
+
...sangamTailwind.content,
|
|
125
|
+
"./index.html",
|
|
126
|
+
"./src/**/*.{ts,tsx}",
|
|
127
|
+
"./node_modules/esds-sangam/dist/**/*.{js,mjs}",
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Spreading `...sangamTailwind` gives you the Sangam theme (colors, spacing, radii, shadows), `darkMode` setting, and any plugins — without having to duplicate them.
|
|
133
|
+
|
|
134
|
+
### 5.5 Wire styles — single CSS entry file
|
|
135
|
+
|
|
136
|
+
Use **one** main CSS file as the Tailwind entry (e.g. `src/index.css`). The `@import` must appear **before** the `@tailwind` directives:
|
|
137
|
+
|
|
138
|
+
```css
|
|
139
|
+
/* src/index.css */
|
|
140
|
+
@import "@esds-sangam/styles/tailwind.css"; /* pulls in globals, theme, and Tailwind layers */
|
|
141
|
+
|
|
142
|
+
@tailwind base;
|
|
143
|
+
@tailwind components;
|
|
144
|
+
@tailwind utilities;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> **Common mistakes**
|
|
148
|
+
>
|
|
149
|
+
> - Putting `@tailwind` before `@import` → build error: _"@import must precede all other statements"_
|
|
150
|
+
> - Importing individual Sangam CSS files (e.g. `theme.css`, `dark.css`) directly in `main.tsx` → each file is a separate PostCSS pass, causing _"@layer base used but no matching @tailwind base directive"_ errors
|
|
151
|
+
|
|
152
|
+
### 5.6 App entry — import only your CSS file
|
|
153
|
+
|
|
154
|
+
In `src/main.tsx`, import only your single CSS file. Do **not** import Sangam CSS files directly here:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// src/main.tsx
|
|
158
|
+
import "./index.css"; // ← only this; Sangam styles are pulled in via @import inside index.css
|
|
159
|
+
|
|
160
|
+
import React from "react";
|
|
161
|
+
import ReactDOM from "react-dom/client";
|
|
162
|
+
import App from "./App";
|
|
163
|
+
|
|
164
|
+
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
165
|
+
<React.StrictMode>
|
|
166
|
+
<App />
|
|
167
|
+
</React.StrictMode>
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 5.7 Dark theme (optional)
|
|
172
|
+
|
|
173
|
+
To support dark mode, add the dark theme import inside `src/index.css`:
|
|
174
|
+
|
|
175
|
+
```css
|
|
176
|
+
/* src/index.css */
|
|
177
|
+
@import "@esds-sangam/styles/tailwind.css";
|
|
178
|
+
@import "@esds-sangam/styles/dark.css"; /* add this line */
|
|
179
|
+
|
|
180
|
+
@tailwind base;
|
|
181
|
+
@tailwind components;
|
|
182
|
+
@tailwind utilities;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Toggle dark mode at runtime by adding or removing the `dark` class on `<html>`:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
document.documentElement.classList.toggle("dark");
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 5.8 Setup checklist
|
|
192
|
+
|
|
193
|
+
| Step | Action |
|
|
194
|
+
| ---- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
195
|
+
| 1 | Install `sangam-ui`, `@esds-sangam/styles`, `@esds-sangam/tokens`, `@esds-sangam/icons` |
|
|
196
|
+
| 2 | Install `tailwindcss`, `postcss`, `autoprefixer`, `postcss-import` as dev dependencies |
|
|
197
|
+
| 3 | In `postcss.config.cjs` — put `postcss-import` **first**, then `tailwindcss`, then `autoprefixer` |
|
|
198
|
+
| 4 | In `tailwind.config.cjs` — spread `sangamTailwind` and extend `content` to include `src/**/*` and `esds-sangam/dist/**/*` |
|
|
199
|
+
| 5 | In `src/index.css` — `@import "@esds-sangam/styles/tailwind.css"` **first**, then `@tailwind` directives |
|
|
200
|
+
| 6 | In `src/main.tsx` — only `import "./index.css"`, no direct Sangam CSS imports |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 6. Usage Guide
|
|
205
|
+
|
|
206
|
+
### 6.1 Quick Start
|
|
207
|
+
|
|
208
|
+
Once setup is complete, import and use Sangam components anywhere in your app:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { Button, Input, Badge } from "sangam-ui";
|
|
212
|
+
|
|
213
|
+
export function App() {
|
|
214
|
+
return (
|
|
215
|
+
<main className="p-6 space-y-4">
|
|
216
|
+
<Input label="Project name" placeholder="my-project" />
|
|
217
|
+
<div className="flex items-center gap-3">
|
|
218
|
+
<Button variant="primary" size="big">
|
|
219
|
+
Deploy
|
|
220
|
+
</Button>
|
|
221
|
+
<Badge size="small" variant="pill" state="success">
|
|
222
|
+
Live
|
|
223
|
+
</Badge>
|
|
224
|
+
</div>
|
|
225
|
+
</main>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 6.2 Full app example
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
// src/App.tsx
|
|
234
|
+
import {
|
|
235
|
+
Button,
|
|
236
|
+
Card,
|
|
237
|
+
Input,
|
|
238
|
+
Dropdown,
|
|
239
|
+
SearchField,
|
|
240
|
+
Checkbox,
|
|
241
|
+
Badge,
|
|
242
|
+
Toast,
|
|
243
|
+
Loader,
|
|
244
|
+
Skeleton,
|
|
245
|
+
Toggle,
|
|
246
|
+
} from "sangam-ui";
|
|
247
|
+
|
|
248
|
+
function App() {
|
|
249
|
+
return (
|
|
250
|
+
<main className="mx-auto flex max-w-3xl flex-col gap-6 p-6">
|
|
251
|
+
<header className="flex items-center justify-between gap-4">
|
|
252
|
+
<div>
|
|
253
|
+
<h1 className="text-2xl font-semibold">ESDS Console</h1>
|
|
254
|
+
<p className="text-sm text-neutral-600">Manage resources across your ESDS projects.</p>
|
|
255
|
+
</div>
|
|
256
|
+
<Button variant="primary" size="big">
|
|
257
|
+
Create resource
|
|
258
|
+
</Button>
|
|
259
|
+
</header>
|
|
260
|
+
|
|
261
|
+
<Card className="space-y-4">
|
|
262
|
+
<Input label="Filter by name" placeholder="production-api" />
|
|
263
|
+
<div className="flex items-center justify-between gap-4">
|
|
264
|
+
<Dropdown
|
|
265
|
+
label="Status"
|
|
266
|
+
placeholder="All statuses"
|
|
267
|
+
options={[
|
|
268
|
+
{ value: "running", label: "Running" },
|
|
269
|
+
{ value: "stopped", label: "Stopped" },
|
|
270
|
+
]}
|
|
271
|
+
/>
|
|
272
|
+
<SearchField placeholder="Search resources" />
|
|
273
|
+
</div>
|
|
274
|
+
</Card>
|
|
275
|
+
</main>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 7. Component Examples
|
|
283
|
+
|
|
284
|
+
### 7.1 Button
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
import { Button } from "sangam-ui";
|
|
288
|
+
import { ChevronDown, Close } from "@esds-sangam/icons";
|
|
289
|
+
|
|
290
|
+
export function ButtonExamples() {
|
|
291
|
+
return (
|
|
292
|
+
<div className="flex flex-wrap gap-4">
|
|
293
|
+
<Button variant="primary" size="big">
|
|
294
|
+
Primary action
|
|
295
|
+
</Button>
|
|
296
|
+
<Button variant="secondary" size="big">
|
|
297
|
+
Secondary
|
|
298
|
+
</Button>
|
|
299
|
+
<Button variant="danger" size="big" icon={<Close />} leadingIcon>
|
|
300
|
+
Delete
|
|
301
|
+
</Button>
|
|
302
|
+
<Button variant="link" size="small" icon={<ChevronDown />} trailingIcon>
|
|
303
|
+
View more
|
|
304
|
+
</Button>
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 7.2 Input
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import { useState } from "react";
|
|
314
|
+
import { Input } from "sangam-ui";
|
|
315
|
+
|
|
316
|
+
export function InputExample() {
|
|
317
|
+
const [value, setValue] = useState("");
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<div className="space-y-4 max-w-sm">
|
|
321
|
+
<Input
|
|
322
|
+
label="Project name"
|
|
323
|
+
required
|
|
324
|
+
placeholder="Enter a project name"
|
|
325
|
+
helperText="This will be visible across your ESDS account."
|
|
326
|
+
value={value}
|
|
327
|
+
onChange={(e) => setValue(e.target.value)}
|
|
328
|
+
/>
|
|
329
|
+
<Input
|
|
330
|
+
label="API key"
|
|
331
|
+
type="password"
|
|
332
|
+
placeholder="••••••••••"
|
|
333
|
+
error={value.length > 0 && value.length < 8}
|
|
334
|
+
helperText={
|
|
335
|
+
value.length > 0 && value.length < 8
|
|
336
|
+
? "API key must be at least 8 characters."
|
|
337
|
+
: "Keep this key secret."
|
|
338
|
+
}
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 7.3 Dropdown
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import { useState } from "react";
|
|
349
|
+
import { Dropdown } from "sangam-ui";
|
|
350
|
+
|
|
351
|
+
const options = [
|
|
352
|
+
{ value: "prod", label: "Production" },
|
|
353
|
+
{ value: "staging", label: "Staging" },
|
|
354
|
+
{ value: "dev", label: "Development" },
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
export function DropdownExample() {
|
|
358
|
+
const [value, setValue] = useState<string | null>(null);
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<Dropdown
|
|
362
|
+
label="Environment"
|
|
363
|
+
placeholder="Select environment"
|
|
364
|
+
required
|
|
365
|
+
options={options}
|
|
366
|
+
value={value}
|
|
367
|
+
onChange={setValue}
|
|
368
|
+
helperText="This environment will be used for the next deployment."
|
|
369
|
+
showHelperIcon
|
|
370
|
+
/>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 7.4 SearchField
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
import { useEffect, useState } from "react";
|
|
379
|
+
import { SearchField, Loader } from "sangam-ui";
|
|
380
|
+
|
|
381
|
+
export function SearchFieldExample() {
|
|
382
|
+
const [query, setQuery] = useState("");
|
|
383
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
384
|
+
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if (!query) return;
|
|
387
|
+
setIsLoading(true);
|
|
388
|
+
const timeout = setTimeout(() => setIsLoading(false), 600);
|
|
389
|
+
return () => clearTimeout(timeout);
|
|
390
|
+
}, [query]);
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<div className="space-y-3 max-w-md">
|
|
394
|
+
<SearchField
|
|
395
|
+
placeholder="Search resources"
|
|
396
|
+
value={query}
|
|
397
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
398
|
+
showClearButton
|
|
399
|
+
/>
|
|
400
|
+
<div className="flex items-center gap-2 text-sm text-neutral-600">
|
|
401
|
+
{isLoading && <Loader size="small" aria-label="Searching" />}
|
|
402
|
+
<span>{isLoading ? "Searching…" : "Type to search across all resources."}</span>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 7.5 Checkbox
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
import { useState } from "react";
|
|
413
|
+
import { Checkbox } from "sangam-ui";
|
|
414
|
+
|
|
415
|
+
export function CheckboxExample() {
|
|
416
|
+
const [marketing, setMarketing] = useState(false);
|
|
417
|
+
const [terms, setTerms] = useState(false);
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<div className="space-y-3">
|
|
421
|
+
<Checkbox
|
|
422
|
+
title="Email updates"
|
|
423
|
+
subtext="Product tips, release notes, and marketing messages."
|
|
424
|
+
checked={marketing}
|
|
425
|
+
onCheckedChange={setMarketing}
|
|
426
|
+
/>
|
|
427
|
+
<Checkbox
|
|
428
|
+
title="I agree to the Terms of Service"
|
|
429
|
+
checked={terms}
|
|
430
|
+
onCheckedChange={setTerms}
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 7.6 Badge
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
import { Badge } from "sangam-ui";
|
|
441
|
+
|
|
442
|
+
export function BadgeExample() {
|
|
443
|
+
return (
|
|
444
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
445
|
+
<Badge size="small" variant="pill" state="info">
|
|
446
|
+
New
|
|
447
|
+
</Badge>
|
|
448
|
+
<Badge size="small" variant="rounded" state="warning" isSolid>
|
|
449
|
+
Beta
|
|
450
|
+
</Badge>
|
|
451
|
+
<Badge size="small" variant="notification" state="error" isSolid>
|
|
452
|
+
3
|
|
453
|
+
</Badge>
|
|
454
|
+
</div>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 7.7 Toast
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
import { useState } from "react";
|
|
463
|
+
import { Button, Toast } from "sangam-ui";
|
|
464
|
+
|
|
465
|
+
export function ToastExample() {
|
|
466
|
+
const [visible, setVisible] = useState(false);
|
|
467
|
+
|
|
468
|
+
function handleSave() {
|
|
469
|
+
setVisible(true);
|
|
470
|
+
setTimeout(() => setVisible(false), 2500);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<div className="space-y-4">
|
|
475
|
+
<Button variant="primary" onClick={handleSave}>
|
|
476
|
+
Save settings
|
|
477
|
+
</Button>
|
|
478
|
+
{visible && (
|
|
479
|
+
<div className="fixed bottom-6 right-6 z-50">
|
|
480
|
+
<Toast
|
|
481
|
+
variant="success"
|
|
482
|
+
title="Settings saved"
|
|
483
|
+
description="Your changes have been applied."
|
|
484
|
+
showClose
|
|
485
|
+
/>
|
|
486
|
+
</div>
|
|
487
|
+
)}
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 7.8 Loader
|
|
494
|
+
|
|
495
|
+
```tsx
|
|
496
|
+
import { useState } from "react";
|
|
497
|
+
import { Button, Loader, Card } from "sangam-ui";
|
|
498
|
+
|
|
499
|
+
export function LoaderExample() {
|
|
500
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
501
|
+
|
|
502
|
+
function handleClick() {
|
|
503
|
+
setIsLoading(true);
|
|
504
|
+
setTimeout(() => setIsLoading(false), 1500);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return (
|
|
508
|
+
<div className="space-y-6 max-w-md">
|
|
509
|
+
<Card className="flex items-center justify-center gap-3 py-8">
|
|
510
|
+
{isLoading ? (
|
|
511
|
+
<>
|
|
512
|
+
<Loader size="small" aria-label="Loading data" />
|
|
513
|
+
<span className="text-sm text-neutral-700">Fetching latest metrics…</span>
|
|
514
|
+
</>
|
|
515
|
+
) : (
|
|
516
|
+
<span className="text-sm text-neutral-700">Metrics are up to date.</span>
|
|
517
|
+
)}
|
|
518
|
+
</Card>
|
|
519
|
+
<Button variant="primary" size="big" onClick={handleClick} disabled={isLoading}>
|
|
520
|
+
{isLoading ? "Refreshing…" : "Refresh data"}
|
|
521
|
+
</Button>
|
|
522
|
+
</div>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### 7.9 Skeleton
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
import { useState, useEffect } from "react";
|
|
531
|
+
import { Card, Skeleton } from "sangam-ui";
|
|
532
|
+
|
|
533
|
+
export function SkeletonExample() {
|
|
534
|
+
const [loaded, setLoaded] = useState(false);
|
|
535
|
+
|
|
536
|
+
useEffect(() => {
|
|
537
|
+
const timeout = setTimeout(() => setLoaded(true), 1200);
|
|
538
|
+
return () => clearTimeout(timeout);
|
|
539
|
+
}, []);
|
|
540
|
+
|
|
541
|
+
return (
|
|
542
|
+
<Card className="w-full max-w-md p-4 space-y-3">
|
|
543
|
+
{loaded ? (
|
|
544
|
+
<>
|
|
545
|
+
<h2 className="text-base font-semibold">Usage by region</h2>
|
|
546
|
+
<p className="text-sm text-neutral-600">
|
|
547
|
+
Your workload is evenly distributed across all configured regions.
|
|
548
|
+
</p>
|
|
549
|
+
</>
|
|
550
|
+
) : (
|
|
551
|
+
<>
|
|
552
|
+
<Skeleton className="h-5 w-1/2" />
|
|
553
|
+
<Skeleton className="h-4 w-full" />
|
|
554
|
+
<Skeleton className="h-4 w-5/6" />
|
|
555
|
+
</>
|
|
556
|
+
)}
|
|
557
|
+
</Card>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### 7.10 Toggle
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
import { useState } from "react";
|
|
566
|
+
import { Toggle } from "sangam-ui";
|
|
567
|
+
|
|
568
|
+
export function ToggleExample() {
|
|
569
|
+
const [autoscale, setAutoscale] = useState(true);
|
|
570
|
+
const [alerts, setAlerts] = useState(false);
|
|
571
|
+
|
|
572
|
+
return (
|
|
573
|
+
<div className="space-y-4">
|
|
574
|
+
<div className="flex items-center justify-between gap-4">
|
|
575
|
+
<div>
|
|
576
|
+
<p className="text-sm font-medium text-neutral-900">Autoscale</p>
|
|
577
|
+
<p className="text-xs text-neutral-600">
|
|
578
|
+
Automatically scale instances up and down based on load.
|
|
579
|
+
</p>
|
|
580
|
+
</div>
|
|
581
|
+
<Toggle size="big" checked={autoscale} onCheckedChange={setAutoscale} />
|
|
582
|
+
</div>
|
|
583
|
+
<div className="flex items-center justify-between gap-4">
|
|
584
|
+
<div>
|
|
585
|
+
<p className="text-sm font-medium text-neutral-900">Email alerts</p>
|
|
586
|
+
<p className="text-xs text-neutral-600">
|
|
587
|
+
Get notified when usage crosses configured thresholds.
|
|
588
|
+
</p>
|
|
589
|
+
</div>
|
|
590
|
+
<Toggle size="small" checked={alerts} onCheckedChange={setAlerts} />
|
|
591
|
+
</div>
|
|
592
|
+
</div>
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## 8. License
|
|
600
|
+
|
|
601
|
+
This project is licensed under the **MIT License**.
|
|
602
|
+
|
|
603
|
+
See the `LICENSE` file at the repository root for the full license text.
|
|
Binary file
|