@juv/codego-react-ui 3.1.5 → 3.1.8

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 CHANGED
@@ -201,6 +201,173 @@ input[type="datetime-local"]::-webkit-inner-spin-button {
201
201
  }
202
202
  ```
203
203
 
204
+ ## in LARAVEL "resources\css\app.css"
205
+ ```css
206
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap');
207
+ @import "tailwindcss";
208
+
209
+ @source "../../resources/js";
210
+ @source "../../node_modules/@juv/codego-react-ui/dist";
211
+
212
+ @theme {
213
+ --font-sans: "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif;
214
+ --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
215
+
216
+ --color-background: var(--background);
217
+ --color-foreground: var(--foreground);
218
+ --color-card: var(--card);
219
+ --color-card-foreground: var(--card-foreground);
220
+ --color-popover: var(--popover);
221
+ --color-popover-foreground: var(--popover-foreground);
222
+ --color-primary: var(--primary);
223
+ --color-primary-hover: var(--primary-hover);
224
+ --color-primary-foreground: var(--primary-foreground);
225
+ --color-secondary: var(--secondary);
226
+ --color-secondary-hover: var(--secondary-hover);
227
+ --color-secondary-foreground: var(--secondary-foreground);
228
+ --color-muted: var(--muted);
229
+ --color-muted-foreground: var(--muted-foreground);
230
+ --color-accent: var(--accent);
231
+ --color-accent-foreground: var(--accent-foreground);
232
+ --color-destructive: var(--destructive);
233
+ --color-destructive-foreground: var(--destructive-foreground);
234
+ --color-border: var(--border);
235
+ --color-input: var(--input);
236
+ --color-ring: var(--ring);
237
+ --color-info: var(--info);
238
+ --color-info-hover: var(--info-hover);
239
+ --color-info-foreground: var(--info-foreground);
240
+ --color-warning: var(--warning);
241
+ --color-warning-hover: var(--warning-hover);
242
+ --color-warning-foreground: var(--warning-foreground);
243
+ --color-danger: var(--danger);
244
+ --color-danger-hover: var(--danger-hover);
245
+ --color-danger-foreground: var(--danger-foreground);
246
+ --color-success: var(--success);
247
+ --color-success-hover: var(--success-hover);
248
+ --color-success-foreground: var(--success-foreground);
249
+ --radius: var(--radius);
250
+ }
251
+
252
+ @layer base {
253
+ :root {
254
+ --background: #ffffff;
255
+ --foreground: #09090b;
256
+ --card: #ffffff;
257
+ --card-foreground: #09090b;
258
+ --popover: #ffffff;
259
+ --popover-foreground: #09090b;
260
+ --primary: #6366f1; /* Indigo 500 */
261
+ --primary-hover: #4f46e5;
262
+ --primary-foreground: #ffffff;
263
+ --secondary: #f4f4f5;
264
+ --secondary-hover: #e4e4e7;
265
+ --secondary-foreground: #18181b;
266
+ --info: #3b82f6; /* Blue 500 */
267
+ --info-hover: #2563eb;
268
+ --info-foreground: #ffffff;
269
+ --warning: #f59e0b; /* Amber 500 */
270
+ --warning-hover: #d97706;
271
+ --warning-foreground: #ffffff;
272
+ --danger: #ef4444; /* Red 500 */
273
+ --danger-hover: #dc2626;
274
+ --danger-foreground: #ffffff;
275
+ --success: #22c55e; /* Green 500 */
276
+ --success-hover: #16a34a;
277
+ --success-foreground: #ffffff;
278
+ --muted: #f4f4f5;
279
+ --muted-foreground: #71717a;
280
+ --accent: #f4f4f5;
281
+ --accent-foreground: #18181b;
282
+ --destructive: #ef4444;
283
+ --destructive-foreground: #ffffff;
284
+ --border: #e4e4e7;
285
+ --input: #e4e4e7;
286
+ --ring: var(--primary);
287
+ --radius: 0.75rem;
288
+ }
289
+
290
+ .dark {
291
+ --background: #030303; /* Deep black */
292
+ --foreground: #ededed;
293
+ --card: #0a0a0a; /* Slightly lighter black */
294
+ --card-foreground: #ededed;
295
+ --popover: #0a0a0a;
296
+ --popover-foreground: #ededed;
297
+ --primary: #8b5cf6; /* Neon Violet */
298
+ --primary-hover: #7c3aed;
299
+ --primary-foreground: #ffffff;
300
+ --secondary: #171717;
301
+ --secondary-hover: #262626;
302
+ --secondary-foreground: #ededed;
303
+ --info: #3b82f6;
304
+ --info-hover: #2563eb;
305
+ --info-foreground: #ffffff;
306
+ --warning: #f59e0b;
307
+ --warning-hover: #d97706;
308
+ --warning-foreground: #ffffff;
309
+ --danger: #ef4444;
310
+ --danger-hover: #dc2626;
311
+ --danger-foreground: #ffffff;
312
+ --success: #22c55e;
313
+ --success-hover: #16a34a;
314
+ --success-foreground: #ffffff;
315
+ --muted: #171717;
316
+ --muted-foreground: #a1a1aa;
317
+ --accent: #1f1f2e; /* Subtle purple tint */
318
+ --accent-foreground: #c4b5fd;
319
+ --destructive: #7f1d1d;
320
+ --destructive-foreground: #ededed;
321
+ --border: #1f1f1f;
322
+ --input: #1f1f1f;
323
+ --ring: var(--primary);
324
+ }
325
+ }
326
+
327
+ @layer base {
328
+ * {
329
+ @apply border-border;
330
+ }
331
+ body {
332
+ @apply bg-background text-foreground;
333
+ font-feature-settings: "rlig" 1, "calt" 1;
334
+ }
335
+ }
336
+
337
+ @layer utilities {
338
+ .glass {
339
+ @apply bg-background/60 backdrop-blur-xl border border-white/10;
340
+ }
341
+ .dark .glass {
342
+ @apply bg-black/40 border-white/5;
343
+ }
344
+ .text-gradient {
345
+ background: linear-gradient(to right, var(--primary), var(--info));
346
+ -webkit-background-clip: text;
347
+ -webkit-text-fill-color: transparent;
348
+ background-clip: text;
349
+ }
350
+ .glow {
351
+ box-shadow: 0 0 20px -5px var(--primary);
352
+ }
353
+ }
354
+ /* Hide calendar icon (Chrome, Edge, Safari) */
355
+ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
356
+ display: none;
357
+ -webkit-appearance: none;
358
+ }
359
+
360
+ /* Optional: remove clear (X) button */
361
+ input[type="datetime-local"]::-webkit-clear-button {
362
+ display: none;
363
+ }
364
+
365
+ /* Optional: remove spin buttons */
366
+ input[type="datetime-local"]::-webkit-inner-spin-button {
367
+ display: none;
368
+ }
369
+
370
+ ```
204
371
  ### 3. Lucide React (icons)
205
372
 
206
373
  ```bash
@@ -257,6 +424,40 @@ import { Button, Card, Modal } from "@juv/codego-react-ui"
257
424
 
258
425
  ---
259
426
 
427
+ ## Form Validation Props
428
+
429
+ All form input components support two shared validation props:
430
+
431
+ | Prop | Type | Description |
432
+ |---|---|---|
433
+ | `required` | `boolean` | Marks the field as required. Shows a `*` indicator next to the label. Also sets `aria-required` on the underlying input. |
434
+ | `error` | `string` | External error message rendered below the field in destructive color. Also sets `aria-invalid` on the underlying input. |
435
+
436
+ Applies to: `Input`, `Select`, `Textarea`, `Checkbox`, `ToggleSwitch`, `RadioGroup`, `Combobox`, `TagInput`, `OtpInput`, `ColorPicker`, `Slider`, `RangeSlider`, `FileUpload`.
437
+
438
+ ```tsx
439
+ // Required field with * indicator
440
+ <Input label="Email" required placeholder="you@example.com" />
441
+
442
+ // External error message (e.g. from server validation)
443
+ <Input label="Email" required error="Email is already taken" />
444
+
445
+ // Works on all form components
446
+ <Select label="Role" required error="Please select a role" options={options} />
447
+ <Checkbox inline label="Accept terms" required error="You must accept the terms" />
448
+ <ToggleSwitch inline label="Enable 2FA" required />
449
+ <Textarea label="Bio" required error="Bio is required" />
450
+ <RadioGroup options={plans} required error="Please select a plan" />
451
+ <Combobox options={frameworks} required error="Please select a framework" />
452
+ <TagInput required error="At least one tag is required" />
453
+ <OtpInput length={6} required error="Please enter the code" />
454
+ <ColorPicker required error="Please select a color" />
455
+ <Slider label="Volume" required error="Please set a value" />
456
+ <FileUpload label="Avatar" required error="Please upload a file" />
457
+ ```
458
+
459
+ ---
460
+
260
461
  ## Components
261
462
 
262
463
  | Component | Export(s) |
@@ -265,6 +466,7 @@ import { Button, Card, Modal } from "@juv/codego-react-ui"
265
466
  | Avatar Stack | `AvatarStack` |
266
467
  | Badge | `Badge` |
267
468
  | Breadcrumb | `Breadcrumb` |
469
+ | Bulletin Board | `BulletinBoard`, `BulletinPreview`, `useServerBulletin` |
268
470
  | Button | `Button` |
269
471
  | Calendar | `Calendar` |
270
472
  | Card | `Card` |
@@ -320,6 +522,105 @@ import { Button, Card, Modal } from "@juv/codego-react-ui"
320
522
 
321
523
  ---
322
524
 
525
+ ## BulletinBoard Props
526
+
527
+ | Prop | Type | Default | Description |
528
+ |---|---|---|---|
529
+ | `items` | `BulletinItem[]` | — | ✓ Array of bulletin post items. |
530
+ | `layout` | `"grid" \| "list" \| "masonry"` | `"grid"` | Board layout mode. |
531
+ | `columns` | `1 \| 2 \| 3 \| 4` | `3` | Number of grid columns (responsive). |
532
+ | `variant` | `"card" \| "minimal" \| "bordered"` | `"card"` | Visual style of each post card. |
533
+ | `searchable` | `boolean` | `false` | Show a search input above the board. |
534
+ | `filterable` | `boolean` | `false` | Show category filter chips above the board. |
535
+ | `categories` | `string[]` | | Explicit category list. Auto-derived from items if omitted. |
536
+ | `title` | `ReactNode` | `"Bulletin Board"` | Board header title. |
537
+ | `headerAction` | `ReactNode` | | Trailing element in the board header (e.g. a New Post button). |
538
+ | `showHeader` | `boolean` | `true` | Show or hide the board header bar. |
539
+ | `emptyMessage` | `ReactNode` | `"No posts found."` | Content shown when the filtered list is empty. |
540
+ | `loading` | `boolean` | `false` | Show skeleton cards instead of real content. |
541
+ | `loadingCount` | `number` | `6` | Number of skeleton cards to render while loading. |
542
+ | `preview` | `boolean` | `false` | Open a `BulletinPreview` modal when a card is clicked. |
543
+ | `onEdit` | `(item: BulletinItem) => void` | | Called when the Edit button is clicked inside the preview. |
544
+ | `onDelete` | `(item: BulletinItem) => void` | | Called after a successful delete (or when no `deleteBaseUrl` is set). |
545
+ | `deleteBaseUrl` | `string` | | Base URL for built-in `DELETE {baseUrl}/{id}/delete` request. |
546
+ | `deleteIdKey` | `string` | `"id"` | Item key used as the id segment in the delete URL. |
547
+ | `serverPagination` | `BulletinServerPaginationProp \| null` | | Pass the `serverPagination` from `useServerBulletin` to enable server-driven pagination. |
548
+ | `onItemClick` | `(item: BulletinItem) => void` | | Fired when a card is clicked (ignored when `preview=true`). |
549
+ | `className` | `string` | | Additional CSS classes on the outer wrapper. |
550
+
551
+ ### BulletinPreview Props
552
+
553
+ | Prop | Type | Required | Description |
554
+ |---|---|---|---|
555
+ | `item` | `BulletinItem` | ✓ | The bulletin item to display. |
556
+ | `onClose` | `() => void` | ✓ | Called when the modal is dismissed. |
557
+ | `onEdit` | `(item: BulletinItem) => void` | | Show an Edit button; called when clicked. |
558
+ | `onDelete` | `(item: BulletinItem) => void` | | Show a Delete button; called when clicked. |
559
+
560
+ ### useServerBulletin Options
561
+
562
+ | Option | Type | Required | Description |
563
+ |---|---|---|---|
564
+ | `url` | `string` | ✓ | API endpoint. `?page=N` appended automatically. |
565
+ | `params` | `Record<string, string \| number>` | | Extra query params merged on every request. |
566
+ | `encrypt` | `boolean` | | Expect a Laravel-encrypted response payload. |
567
+ | `key` | `string` | | Laravel `APP_KEY` for decryption. |
568
+ | `decryptPayloadLog` | `boolean` | | Log the decrypted payload to the console. |
569
+ | `transform` | `(row: any) => BulletinItem` | | Map a raw API row to a `BulletinItem`. |
570
+
571
+ ### useServerBulletin Return
572
+
573
+ | Field | Type | Description |
574
+ |---|---|---|
575
+ | `items` | `BulletinItem[]` | Fetched (and optionally transformed) items. |
576
+ | `loading` | `boolean` | `true` while the request is in-flight. |
577
+ | `error` | `string \| null` | Error message if the request failed. |
578
+ | `pagination` | `ServerPagination \| null` | Raw pagination metadata. |
579
+ | `serverPagination` | `BulletinServerPaginationProp \| null` | Pass directly as `<BulletinBoard serverPagination={...} />`. |
580
+ | `goToPage` | `(page: number) => void` | Navigate to a specific page. |
581
+ | `reload` | `() => void` | Re-fetch the current page (e.g. after a delete). |
582
+
583
+ ### useServerBulletin Example
584
+
585
+ ```tsx
586
+ import { BulletinBoard, useServerBulletin } from "@juv/codego-react-ui"
587
+
588
+ function AnnouncementsPage() {
589
+ const { items, loading, serverPagination, reload } = useServerBulletin({
590
+ url: "/api/bulletins",
591
+ params: { per_page: 9 },
592
+ transform: (row) => ({
593
+ id: row.id,
594
+ title: row.subject,
595
+ body: row.content,
596
+ author: row.posted_by,
597
+ date: row.created_at,
598
+ category: row.department,
599
+ priority: row.level,
600
+ pinned: row.is_pinned,
601
+ tags: row.tags ?? [],
602
+ }),
603
+ })
604
+
605
+ return (
606
+ <BulletinBoard
607
+ items={items}
608
+ loading={loading}
609
+ serverPagination={serverPagination}
610
+ columns={3}
611
+ searchable
612
+ filterable
613
+ preview
614
+ onEdit={(item) => openEditModal(item)}
615
+ deleteBaseUrl="/api/bulletins"
616
+ onDelete={() => reload()}
617
+ />
618
+ )
619
+ }
620
+ ```
621
+
622
+ ---
623
+
323
624
  ## LeafletMap Props
324
625
 
325
626
  | Prop | Type | Default | Description |