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 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