torch-glare-mcp 1.0.4 → 1.0.6
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/docs/components/alert-dialog.md +160 -0
- package/docs/components/date-picker.md +25 -0
- package/docs/components/dialog.md +144 -18
- package/docs/components/{section-card.md → section-block.md} +25 -25
- package/docs/components/toggle.md +59 -0
- package/docs/llms-manifest.json +1 -1
- package/package.json +1 -1
|
@@ -456,6 +456,166 @@ function PermissionAlert() {
|
|
|
456
456
|
| `variant` | `ButtonVariant` | `'RedSecStyle'` | Button variant |
|
|
457
457
|
| `buttonType` | `'button' \| 'icon'` | `'icon'` | Button type |
|
|
458
458
|
|
|
459
|
+
## Known Limitations & Frontend Patterns
|
|
460
|
+
|
|
461
|
+
### Every AlertDialog subcomponent ships without panel/typography defaults — and worse than `Dialog`
|
|
462
|
+
|
|
463
|
+
`AlertDialogContent` ships with `p-[12px]` (too tight), `AlertDialogHeader` with `flex justify-between` (instead of `flex-col` for stacked title+description), `AlertDialogFooter` with `flex-col-reverse sm:flex-row` (vertical-on-mobile), and `AlertDialogDescription` with the bizarre `bg-background-presentation-form-base border ... rounded-[8px] p-[24px_48px_48px_48px]` — a 48 px-padded bordered box wrapped around what should be a one-line subtitle.
|
|
464
|
+
|
|
465
|
+
**Production-tested override** — patch `AlertDialog.tsx` once after `npx torch-glare add AlertDialog`. Same surface-token corrections as `Dialog`: use `bg-background-presentation-body-primary` and `text-content-presentation-global-*`, never `*-system-*` and never `form-base`.
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
"use client";
|
|
469
|
+
|
|
470
|
+
import * as React from "react";
|
|
471
|
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
|
472
|
+
import { cn } from "@/utils/cn";
|
|
473
|
+
|
|
474
|
+
const AlertDialog = AlertDialogPrimitive.Root;
|
|
475
|
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
|
476
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
477
|
+
|
|
478
|
+
const AlertDialogOverlay = React.forwardRef<
|
|
479
|
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
480
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
481
|
+
>(({ className, ...props }, ref) => (
|
|
482
|
+
<AlertDialogPrimitive.Overlay
|
|
483
|
+
ref={ref}
|
|
484
|
+
className={cn(
|
|
485
|
+
"fixed inset-0 z-50 bg-black/60",
|
|
486
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
487
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
488
|
+
className,
|
|
489
|
+
)}
|
|
490
|
+
{...props}
|
|
491
|
+
/>
|
|
492
|
+
));
|
|
493
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
494
|
+
|
|
495
|
+
const AlertDialogContent = React.forwardRef<
|
|
496
|
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
497
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
|
498
|
+
>(({ className, ...props }, ref) => (
|
|
499
|
+
<AlertDialogPortal>
|
|
500
|
+
<AlertDialogOverlay />
|
|
501
|
+
<AlertDialogPrimitive.Content
|
|
502
|
+
ref={ref}
|
|
503
|
+
className={cn(
|
|
504
|
+
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
|
|
505
|
+
"flex flex-col items-stretch justify-start",
|
|
506
|
+
"w-[92vw] sm:max-w-md",
|
|
507
|
+
"bg-background-presentation-body-primary",
|
|
508
|
+
"border border-border-presentation-global-primary",
|
|
509
|
+
"rounded-xl shadow-lg",
|
|
510
|
+
"p-0 gap-0 overflow-hidden",
|
|
511
|
+
"duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
512
|
+
className,
|
|
513
|
+
)}
|
|
514
|
+
{...props}
|
|
515
|
+
/>
|
|
516
|
+
</AlertDialogPortal>
|
|
517
|
+
));
|
|
518
|
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
|
519
|
+
|
|
520
|
+
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
521
|
+
<div
|
|
522
|
+
className={cn("flex flex-col space-y-1.5 text-left px-6 pt-5 pb-2", className)}
|
|
523
|
+
{...props}
|
|
524
|
+
/>
|
|
525
|
+
);
|
|
526
|
+
AlertDialogHeader.displayName = "AlertDialogHeader";
|
|
527
|
+
|
|
528
|
+
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
529
|
+
<div
|
|
530
|
+
className={cn("flex flex-row justify-end gap-2 px-6 py-4", className)}
|
|
531
|
+
{...props}
|
|
532
|
+
/>
|
|
533
|
+
);
|
|
534
|
+
AlertDialogFooter.displayName = "AlertDialogFooter";
|
|
535
|
+
|
|
536
|
+
const AlertDialogTitle = React.forwardRef<
|
|
537
|
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
|
538
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
|
539
|
+
>(({ className, ...props }, ref) => (
|
|
540
|
+
<AlertDialogPrimitive.Title
|
|
541
|
+
ref={ref}
|
|
542
|
+
className={cn(
|
|
543
|
+
"typography-headers-small-semibold text-content-presentation-global-primary",
|
|
544
|
+
className,
|
|
545
|
+
)}
|
|
546
|
+
{...props}
|
|
547
|
+
/>
|
|
548
|
+
));
|
|
549
|
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
|
550
|
+
|
|
551
|
+
const AlertDialogDescription = React.forwardRef<
|
|
552
|
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
|
553
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
|
554
|
+
>(({ className, ...props }, ref) => (
|
|
555
|
+
<AlertDialogPrimitive.Description
|
|
556
|
+
ref={ref}
|
|
557
|
+
className={cn(
|
|
558
|
+
"typography-body-small-regular text-content-presentation-global-secondary px-6 pb-2",
|
|
559
|
+
className,
|
|
560
|
+
)}
|
|
561
|
+
{...props}
|
|
562
|
+
/>
|
|
563
|
+
));
|
|
564
|
+
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
|
|
565
|
+
|
|
566
|
+
const AlertDialogAction = AlertDialogPrimitive.Action;
|
|
567
|
+
const AlertDialogCancel = AlertDialogPrimitive.Cancel;
|
|
568
|
+
|
|
569
|
+
export {
|
|
570
|
+
AlertDialog,
|
|
571
|
+
AlertDialogTrigger,
|
|
572
|
+
AlertDialogPortal,
|
|
573
|
+
AlertDialogOverlay,
|
|
574
|
+
AlertDialogContent,
|
|
575
|
+
AlertDialogHeader,
|
|
576
|
+
AlertDialogFooter,
|
|
577
|
+
AlertDialogTitle,
|
|
578
|
+
AlertDialogDescription,
|
|
579
|
+
AlertDialogAction,
|
|
580
|
+
AlertDialogCancel,
|
|
581
|
+
};
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Critical default decisions:
|
|
585
|
+
|
|
586
|
+
- **Drop the bordered box on `AlertDialogDescription`.** The shipped default wraps the description in a 48 px-padded bordered box, which produces a visually heavy nested panel. Description is a one-line subtitle — same typography pattern as `DialogDescription`.
|
|
587
|
+
- **`AlertDialogHeader` → `flex flex-col` (NOT `flex justify-between`)** so title and description stack with proper rhythm.
|
|
588
|
+
- **`AlertDialogFooter` → `flex-row justify-end`** on all viewports, no `flex-col-reverse sm:flex-row` weirdness.
|
|
589
|
+
- **`AlertDialogDescription` carries `px-6 pb-2`** since alert dialogs typically don't have a separate body section between header and footer.
|
|
590
|
+
- Surface and text tokens are **identical to `Dialog`** — `bg-background-presentation-body-primary` + `text-content-presentation-global-primary` + `text-content-presentation-global-secondary`. Never `*-system-*`. Never `form-base`. Never `action-secondary`.
|
|
591
|
+
|
|
592
|
+
### Standard usage after the override
|
|
593
|
+
|
|
594
|
+
Once the patches above are in place, every consumer renders correctly with bare components:
|
|
595
|
+
|
|
596
|
+
```tsx
|
|
597
|
+
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
598
|
+
<AlertDialogContent>
|
|
599
|
+
<AlertDialogHeader>
|
|
600
|
+
<AlertDialogTitle>Delete this template?</AlertDialogTitle>
|
|
601
|
+
<AlertDialogDescription>
|
|
602
|
+
This action cannot be undone. The template will be permanently removed.
|
|
603
|
+
</AlertDialogDescription>
|
|
604
|
+
</AlertDialogHeader>
|
|
605
|
+
<AlertDialogFooter>
|
|
606
|
+
<AlertDialogCancel asChild>
|
|
607
|
+
<Button type="button" variant="BorderStyle" size="M">Cancel</Button>
|
|
608
|
+
</AlertDialogCancel>
|
|
609
|
+
<AlertDialogAction asChild>
|
|
610
|
+
<Button type="button" variant="RedColStyle" size="M" onClick={handleDelete}>
|
|
611
|
+
Delete
|
|
612
|
+
</Button>
|
|
613
|
+
</AlertDialogAction>
|
|
614
|
+
</AlertDialogFooter>
|
|
615
|
+
</AlertDialogContent>
|
|
616
|
+
</AlertDialog>
|
|
617
|
+
```
|
|
618
|
+
|
|
459
619
|
## Variants
|
|
460
620
|
|
|
461
621
|
### Default Variant
|
|
@@ -615,6 +615,31 @@ The `as unknown as Date` shape is required — TypeScript will reject `as Date`
|
|
|
615
615
|
|
|
616
616
|
The shipped runtime uses `string` for `hour`, `minute`, and `time` (`"AM" | "PM"`). If you derive your own state from `TimePickerValue`, type those fields as `string`, not `number`, regardless of what older docs claim.
|
|
617
617
|
|
|
618
|
+
### Internal `<Picker>` value/onChange types are incompatible with `TimePickerValue` — `tsc` fails out of the box
|
|
619
|
+
|
|
620
|
+
`DatePicker.tsx` wraps `torch-react-mobile-picker`'s `<Picker>` and passes a concrete `TimePickerValue` (`{ hour, minute, time }`) where the picker's generic `PickerValue` (`Record<string, string>`) is expected. Strict TypeScript builds fail immediately:
|
|
621
|
+
|
|
622
|
+
```
|
|
623
|
+
DatePicker.tsx: Type 'TimePickerValue' is not assignable to type 'PickerValue'.
|
|
624
|
+
Index signature for type 'string' is missing in type 'TimePickerValue'.
|
|
625
|
+
DatePicker.tsx: Type '(e: TimePickerValue) => void' is not assignable to type
|
|
626
|
+
'(value: PickerValue, key: string) => void'.
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Workaround — add `as any` casts at the `<Picker>` boundary** (and only there, so consumer types stay honest):
|
|
630
|
+
|
|
631
|
+
```tsx
|
|
632
|
+
<Picker
|
|
633
|
+
value={value as any}
|
|
634
|
+
onChange={((e: TimePickerValue) => {
|
|
635
|
+
onChange(e);
|
|
636
|
+
}) as any}
|
|
637
|
+
wheelMode="normal"
|
|
638
|
+
>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Patch this manually after `npx torch-glare add DatePicker` — otherwise `pnpm build` (which runs `tsc -b`) will fail. The same `Ref` import in the file is also unused and trips `noUnusedLocals`; remove it while you're in there.
|
|
642
|
+
|
|
618
643
|
### `npx torch-glare add DatePicker` does not install `utils/dateFormat.ts`
|
|
619
644
|
|
|
620
645
|
The CLI ships `DatePicker.tsx` without copying the `dateFormat.ts` utility it imports, so the component fails to build immediately after install:
|
|
@@ -484,31 +484,63 @@ export const DialogCloseButton: React.ForwardRefExoticComponent<
|
|
|
484
484
|
|
|
485
485
|
## Known Limitations & Frontend Patterns
|
|
486
486
|
|
|
487
|
-
###
|
|
487
|
+
### Every Dialog subcomponent ships without panel/typography defaults
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
`DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, and `DialogDescription` all ship without surfaces, padding, rounded corners, or theme-correct typography. The official "bare" example renders an invisible/transparent panel, and the shipped `DialogTitle` defaults to `text-content-system-global-primary` (a light-on-dark token) which renders white-on-white on the correct light surface.
|
|
490
490
|
|
|
491
|
-
**
|
|
491
|
+
**Critical token corrections** (the previous override was wrong):
|
|
492
|
+
|
|
493
|
+
- The right surface is `bg-background-presentation-body-primary` — NOT `bg-background-presentation-form-base` (form-base is for input field surfaces) and NOT `bg-background-system-action-secondary` (that's a brand-dark inverted action surface).
|
|
494
|
+
- The right title token is `text-content-presentation-global-primary` — NOT `text-content-system-global-primary`.
|
|
495
|
+
- The right description token is `text-content-presentation-global-secondary` — NOT the shipped triple-stack `"text-sm text-muted-foreground text-content-system-global-primary"`.
|
|
496
|
+
|
|
497
|
+
**Production-tested override** — patch `Dialog.tsx` once after `npx torch-glare add Dialog`, then every consumer renders correctly with bare components:
|
|
492
498
|
|
|
493
499
|
```tsx
|
|
500
|
+
"use client";
|
|
501
|
+
|
|
502
|
+
import * as React from "react";
|
|
494
503
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
495
504
|
import { cn } from "@/utils/cn";
|
|
496
505
|
|
|
506
|
+
const Dialog = DialogPrimitive.Root;
|
|
507
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
508
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
509
|
+
|
|
510
|
+
const DialogOverlay = React.forwardRef<
|
|
511
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
512
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
513
|
+
>(({ className, ...props }, ref) => (
|
|
514
|
+
<DialogPrimitive.Overlay
|
|
515
|
+
ref={ref}
|
|
516
|
+
className={cn(
|
|
517
|
+
"fixed inset-0 z-50 bg-black/60",
|
|
518
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
519
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
520
|
+
className,
|
|
521
|
+
)}
|
|
522
|
+
{...props}
|
|
523
|
+
/>
|
|
524
|
+
));
|
|
525
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
526
|
+
|
|
497
527
|
const DialogContent = React.forwardRef<
|
|
498
528
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
499
529
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
500
530
|
>(({ className, children, ...props }, ref) => (
|
|
501
|
-
<
|
|
502
|
-
<
|
|
531
|
+
<DialogPortal>
|
|
532
|
+
<DialogOverlay />
|
|
503
533
|
<DialogPrimitive.Content
|
|
504
534
|
ref={ref}
|
|
505
535
|
className={cn(
|
|
506
|
-
"fixed left-[50%] top-[50%] z-50
|
|
507
|
-
"flex flex-col
|
|
536
|
+
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]",
|
|
537
|
+
"flex flex-col items-stretch justify-start",
|
|
538
|
+
"w-[92vw] sm:max-w-lg",
|
|
539
|
+
"bg-background-presentation-body-primary",
|
|
508
540
|
"border border-border-presentation-global-primary",
|
|
509
|
-
"
|
|
510
|
-
"
|
|
511
|
-
"max-h-[90vh]
|
|
541
|
+
"rounded-xl shadow-lg",
|
|
542
|
+
"p-0 gap-0 overflow-hidden",
|
|
543
|
+
"max-h-[90vh]",
|
|
512
544
|
"duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
513
545
|
className,
|
|
514
546
|
)}
|
|
@@ -516,21 +548,115 @@ const DialogContent = React.forwardRef<
|
|
|
516
548
|
>
|
|
517
549
|
{children}
|
|
518
550
|
</DialogPrimitive.Content>
|
|
519
|
-
</
|
|
551
|
+
</DialogPortal>
|
|
552
|
+
));
|
|
553
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
554
|
+
|
|
555
|
+
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
556
|
+
<div
|
|
557
|
+
className={cn("flex flex-col space-y-1.5 text-left px-6 pt-5 pb-4", className)}
|
|
558
|
+
{...props}
|
|
559
|
+
/>
|
|
560
|
+
);
|
|
561
|
+
DialogHeader.displayName = "DialogHeader";
|
|
562
|
+
|
|
563
|
+
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
564
|
+
<div
|
|
565
|
+
className={cn("flex flex-row justify-end gap-2 px-6 py-4", className)}
|
|
566
|
+
{...props}
|
|
567
|
+
/>
|
|
568
|
+
);
|
|
569
|
+
DialogFooter.displayName = "DialogFooter";
|
|
570
|
+
|
|
571
|
+
const DialogTitle = React.forwardRef<
|
|
572
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
573
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
574
|
+
>(({ className, ...props }, ref) => (
|
|
575
|
+
<DialogPrimitive.Title
|
|
576
|
+
ref={ref}
|
|
577
|
+
className={cn(
|
|
578
|
+
"typography-headers-small-semibold text-content-presentation-global-primary",
|
|
579
|
+
className,
|
|
580
|
+
)}
|
|
581
|
+
{...props}
|
|
582
|
+
/>
|
|
583
|
+
));
|
|
584
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
585
|
+
|
|
586
|
+
const DialogDescription = React.forwardRef<
|
|
587
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
588
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
589
|
+
>(({ className, ...props }, ref) => (
|
|
590
|
+
<DialogPrimitive.Description
|
|
591
|
+
ref={ref}
|
|
592
|
+
className={cn(
|
|
593
|
+
"typography-body-small-regular text-content-presentation-global-secondary",
|
|
594
|
+
className,
|
|
595
|
+
)}
|
|
596
|
+
{...props}
|
|
597
|
+
/>
|
|
520
598
|
));
|
|
599
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
600
|
+
|
|
601
|
+
export {
|
|
602
|
+
Dialog,
|
|
603
|
+
DialogTrigger,
|
|
604
|
+
DialogPortal,
|
|
605
|
+
DialogOverlay,
|
|
606
|
+
DialogContent,
|
|
607
|
+
DialogHeader,
|
|
608
|
+
DialogFooter,
|
|
609
|
+
DialogTitle,
|
|
610
|
+
DialogDescription,
|
|
611
|
+
};
|
|
521
612
|
```
|
|
522
613
|
|
|
523
|
-
Critical
|
|
614
|
+
Critical default decisions, and why:
|
|
615
|
+
|
|
616
|
+
- **`bg-background-presentation-body-primary`** for the panel surface — it's the body-surface token that pairs correctly with `text-content-presentation-global-primary` across all themes. `form-base` and `action-secondary` are wrong choices that the previous version of this doc recommended.
|
|
617
|
+
- **`p-0 gap-0 overflow-hidden`** on `DialogContent` — padding is delegated to `DialogHeader`/`DialogFooter` (and the form body). Keeping the content padding-free lets header/footer touch the rounded corners cleanly.
|
|
618
|
+
- **`DialogHeader` → `flex flex-col space-y-1.5 ... px-6 pt-5 pb-4`** — column layout (NOT `flex justify-between` like the shipped default) so title and description stack with a tight 6 px gap. Padding lives here, not on the content.
|
|
619
|
+
- **`DialogFooter` → `flex flex-row justify-end gap-2 px-6 py-4`** — horizontal on all viewports (NOT `flex-col-reverse sm:flex-row` like the shipped default). The shipped vertical-on-mobile pattern looks broken on narrow phones too.
|
|
620
|
+
- **`w-[92vw] sm:max-w-lg`** — 92 % of viewport on mobile, 512 px max from `sm` up. Override per-dialog with `className="sm:max-w-110"` (440 px) or `sm:max-w-2xl` (672 px) for wider forms.
|
|
621
|
+
|
|
622
|
+
### Form body lives outside the form-padded `DialogFooter`
|
|
623
|
+
|
|
624
|
+
Place the `<form>` between `DialogHeader` and `DialogFooter`, with the footer as a **sibling outside the form** so its padding doesn't compound with the form body's padding. Use `type="button"` + `onClick={handleSubmit(onSubmit)}` on the submit button rather than `type="submit"`:
|
|
625
|
+
|
|
626
|
+
```tsx
|
|
627
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
628
|
+
<DialogContent className="sm:max-w-110">
|
|
629
|
+
<DialogHeader>
|
|
630
|
+
<DialogTitle>Create Folder</DialogTitle>
|
|
631
|
+
<DialogDescription>Add a new folder.</DialogDescription>
|
|
632
|
+
</DialogHeader>
|
|
633
|
+
|
|
634
|
+
<form className="flex flex-col gap-4 px-6 py-5">
|
|
635
|
+
<LabelField
|
|
636
|
+
label="Folder Name"
|
|
637
|
+
requiredLabel="*"
|
|
638
|
+
{...register("name")}
|
|
639
|
+
className="w-full *:w-full"
|
|
640
|
+
/>
|
|
641
|
+
</form>
|
|
642
|
+
|
|
643
|
+
<DialogFooter>
|
|
644
|
+
<Button type="button" variant="BorderStyle" size="M" onClick={() => onOpenChange(false)}>
|
|
645
|
+
Cancel
|
|
646
|
+
</Button>
|
|
647
|
+
<Button type="button" variant="PrimeStyle" size="M" onClick={handleSubmit(onSubmit)}>
|
|
648
|
+
Create
|
|
649
|
+
</Button>
|
|
650
|
+
</DialogFooter>
|
|
651
|
+
</DialogContent>
|
|
652
|
+
</Dialog>
|
|
653
|
+
```
|
|
524
654
|
|
|
525
|
-
|
|
526
|
-
- `border border-border-presentation-global-primary rounded-2xl` — visible panel
|
|
527
|
-
- `p-6` — sensible padding
|
|
528
|
-
- `w-full max-w-lg` — consistent 512 px width; override per-dialog with `className="max-w-2xl"` etc.
|
|
529
|
-
- `max-h-[90vh] overflow-y-auto` — long forms scroll instead of getting clipped
|
|
655
|
+
The `px-6 py-5` body padding matches the header/footer's horizontal `px-6` rhythm so the columns line up vertically across the panel.
|
|
530
656
|
|
|
531
657
|
### `DialogTitle` with icon
|
|
532
658
|
|
|
533
|
-
Plain `<DialogTitle>Create Account</DialogTitle>`
|
|
659
|
+
Plain `<DialogTitle>Create Account</DialogTitle>` is fine, but you can pass JSX with an icon for instant entity recognition:
|
|
534
660
|
|
|
535
661
|
```tsx
|
|
536
662
|
<DialogHeader>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
title:
|
|
2
|
+
name: SectionBlock
|
|
3
|
+
title: SectionBlock
|
|
4
4
|
description: Sectioned card container with a colored title badge for grouping related content like forms, tables, or lists.
|
|
5
5
|
category: layout
|
|
6
6
|
group: Layout & Containers
|
|
@@ -11,34 +11,34 @@ dependencies:
|
|
|
11
11
|
- "class-variance-authority": "^0.7.0"
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
#
|
|
14
|
+
# SectionBlock
|
|
15
15
|
|
|
16
16
|
> A card container with an optional colored title badge. Use it to group related content — custom field forms, tables, settings groups — under a clear, color-coded heading.
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
npx torch-glare add
|
|
21
|
+
npx torch-glare add SectionBlock
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Import
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
import {
|
|
27
|
+
import { SectionBlock, type SectionColor } from "@/components/SectionBlock";
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## Basic Usage
|
|
31
31
|
|
|
32
32
|
```tsx
|
|
33
|
-
import {
|
|
33
|
+
import { SectionBlock } from "@/components/SectionBlock";
|
|
34
34
|
|
|
35
35
|
export function Example() {
|
|
36
36
|
return (
|
|
37
|
-
<
|
|
37
|
+
<SectionBlock color="Blue" title="Project details">
|
|
38
38
|
<p className="text-content-presentation-action-light-primary py-4">
|
|
39
39
|
Card body content goes here.
|
|
40
40
|
</p>
|
|
41
|
-
</
|
|
41
|
+
</SectionBlock>
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
```
|
|
@@ -71,7 +71,7 @@ export type SectionColor =
|
|
|
71
71
|
| "Pink"
|
|
72
72
|
| "Gray";
|
|
73
73
|
|
|
74
|
-
export interface
|
|
74
|
+
export interface SectionBlockProps
|
|
75
75
|
extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
76
76
|
color?: SectionColor;
|
|
77
77
|
title?: ReactNode;
|
|
@@ -81,14 +81,14 @@ export interface SectionCardProps
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
The component is a `forwardRef<HTMLDivElement,
|
|
84
|
+
The component is a `forwardRef<HTMLDivElement, SectionBlockProps>`.
|
|
85
85
|
|
|
86
86
|
## Examples
|
|
87
87
|
|
|
88
88
|
### All Colors
|
|
89
89
|
|
|
90
90
|
```tsx
|
|
91
|
-
import {
|
|
91
|
+
import { SectionBlock, type SectionColor } from "@/components/SectionBlock";
|
|
92
92
|
|
|
93
93
|
const COLORS: SectionColor[] = [
|
|
94
94
|
"Blue",
|
|
@@ -105,11 +105,11 @@ export function AllColors() {
|
|
|
105
105
|
return (
|
|
106
106
|
<div className="flex flex-col gap-[24px]">
|
|
107
107
|
{COLORS.map((color) => (
|
|
108
|
-
<
|
|
108
|
+
<SectionBlock key={color} color={color} title={`${color} section`}>
|
|
109
109
|
<p className="text-content-presentation-action-light-primary py-4">
|
|
110
110
|
This is a {color.toLowerCase()} sectioned card.
|
|
111
111
|
</p>
|
|
112
|
-
</
|
|
112
|
+
</SectionBlock>
|
|
113
113
|
))}
|
|
114
114
|
</div>
|
|
115
115
|
);
|
|
@@ -121,11 +121,11 @@ export function AllColors() {
|
|
|
121
121
|
When `title` is omitted, the header is hidden but the body padding remains.
|
|
122
122
|
|
|
123
123
|
```tsx
|
|
124
|
-
<
|
|
124
|
+
<SectionBlock>
|
|
125
125
|
<p className="text-content-presentation-action-light-primary py-4">
|
|
126
|
-
A
|
|
126
|
+
A SectionBlock without a title — header is hidden, body still padded.
|
|
127
127
|
</p>
|
|
128
|
-
</
|
|
128
|
+
</SectionBlock>
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
### Rich Title with Icon
|
|
@@ -133,7 +133,7 @@ When `title` is omitted, the header is hidden but the body padding remains.
|
|
|
133
133
|
`title` accepts any ReactNode, so you can pass JSX with icons, counts, links, etc.
|
|
134
134
|
|
|
135
135
|
```tsx
|
|
136
|
-
<
|
|
136
|
+
<SectionBlock
|
|
137
137
|
color="Purple"
|
|
138
138
|
title={
|
|
139
139
|
<span className="flex items-center gap-[6px]">
|
|
@@ -145,22 +145,22 @@ When `title` is omitted, the header is hidden but the body padding remains.
|
|
|
145
145
|
<p className="text-content-presentation-action-light-primary py-4">
|
|
146
146
|
The title prop is fully customizable.
|
|
147
147
|
</p>
|
|
148
|
-
</
|
|
148
|
+
</SectionBlock>
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
### Custom Fields Form
|
|
152
152
|
|
|
153
|
-
A common pattern: pair `
|
|
153
|
+
A common pattern: pair `SectionBlock` with a row layout to build labeled-field forms.
|
|
154
154
|
|
|
155
155
|
```tsx
|
|
156
156
|
import { type ReactNode } from "react";
|
|
157
|
-
import {
|
|
157
|
+
import { SectionBlock } from "@/components/SectionBlock";
|
|
158
158
|
import { InputField } from "@/components/InputField";
|
|
159
159
|
import { ActionButton } from "@/components/ActionButton";
|
|
160
160
|
|
|
161
161
|
export function CustomFieldsForm() {
|
|
162
162
|
return (
|
|
163
|
-
<
|
|
163
|
+
<SectionBlock
|
|
164
164
|
color="Blue"
|
|
165
165
|
title={
|
|
166
166
|
<span className="flex items-center gap-[6px]">
|
|
@@ -203,7 +203,7 @@ export function CustomFieldsForm() {
|
|
|
203
203
|
/>
|
|
204
204
|
}
|
|
205
205
|
/>
|
|
206
|
-
</
|
|
206
|
+
</SectionBlock>
|
|
207
207
|
);
|
|
208
208
|
}
|
|
209
209
|
|
|
@@ -241,7 +241,7 @@ function RowDivider() {
|
|
|
241
241
|
Use `containerClassName`, `headerClassName`, and `bodyClassName` to override the built-in spacing and width without losing the title/body structure.
|
|
242
242
|
|
|
243
243
|
```tsx
|
|
244
|
-
<
|
|
244
|
+
<SectionBlock
|
|
245
245
|
color="Green"
|
|
246
246
|
title="Compact card"
|
|
247
247
|
containerClassName="w-[600px] pt-[4px] pb-[16px]"
|
|
@@ -250,14 +250,14 @@ Use `containerClassName`, `headerClassName`, and `bodyClassName` to override the
|
|
|
250
250
|
<p className="text-content-presentation-action-light-primary py-2">
|
|
251
251
|
Tighter, narrower variant.
|
|
252
252
|
</p>
|
|
253
|
-
</
|
|
253
|
+
</SectionBlock>
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
## Patterns
|
|
257
257
|
|
|
258
258
|
- **Color coding**: Use distinct colors to help users scan a page of multiple sections (e.g., Blue for primary forms, Yellow for warnings, Red for destructive zones).
|
|
259
259
|
- **No-title sections**: Drop the `title` prop entirely when the section's purpose is obvious from context — keeps the body padding without the visual weight of a header.
|
|
260
|
-
- **Composing with form fields**: `
|
|
260
|
+
- **Composing with form fields**: `SectionBlock` does not impose any inner layout — pair it with helpers like the `FieldRow` pattern above, or with `InputField`, `Form`, or `FieldSection` for more structured forms.
|
|
261
261
|
- **Default width**: The component ships with `w-[1100px]`. Override via `containerClassName="w-full"` (or any specific width) for narrower containers.
|
|
262
262
|
|
|
263
263
|
## Accessibility
|
|
@@ -373,6 +373,65 @@ interface CustomToggleProps extends ToggleProps, VariantProps<typeof toggleVaria
|
|
|
373
373
|
export const Toggle: React.ForwardRefExoticComponent<CustomToggleProps>
|
|
374
374
|
```
|
|
375
375
|
|
|
376
|
+
## Known Limitations & Frontend Patterns
|
|
377
|
+
|
|
378
|
+
> ⚠️ **`Toggle` is icon-only.** Its dimensions are hardcoded squares — `S=22×22`, `M=28×28`, `L=34×34`, `XL=40×40` — so any text label wider than ~3 characters will visually clip. The "Filter Options", "Theme Switcher with text", and similar text-label examples below are misleading: they only render correctly in environments that override the dimensions.
|
|
379
|
+
|
|
380
|
+
### Use `Button` with variant swap for selectable text chips
|
|
381
|
+
|
|
382
|
+
For format selectors (DOCX, MD, RTF), tag pickers, filter pills, or any case where the chip carries text wider than ~3 characters, **do not use `Toggle`**. Use `Button` with a `variant` swap to express the selected state:
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
const FORMATS = ["DOCX", "MD", "RTF", "ODT", "EPUB", "TXT"];
|
|
386
|
+
|
|
387
|
+
export function FormatPicker({ selected, onSelect }: Props) {
|
|
388
|
+
return (
|
|
389
|
+
<div className="flex flex-wrap gap-2">
|
|
390
|
+
{FORMATS.map((format) => {
|
|
391
|
+
const isSelected = selected === format;
|
|
392
|
+
return (
|
|
393
|
+
<Button
|
|
394
|
+
key={format}
|
|
395
|
+
type="button"
|
|
396
|
+
variant={isSelected ? "PrimeStyle" : "BorderStyle"}
|
|
397
|
+
size="S"
|
|
398
|
+
onClick={() => onSelect(format)}
|
|
399
|
+
className="uppercase"
|
|
400
|
+
>
|
|
401
|
+
{format}
|
|
402
|
+
</Button>
|
|
403
|
+
);
|
|
404
|
+
})}
|
|
405
|
+
</div>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
This gives you:
|
|
411
|
+
|
|
412
|
+
- Auto-sizing horizontal padding via the button's `px-*` defaults
|
|
413
|
+
- Theme-correct surface and text tokens (button variants are pre-themed)
|
|
414
|
+
- Clear pressed/unpressed visual via `PrimeStyle` (filled) vs `BorderStyle` (outlined)
|
|
415
|
+
- All the focus/disabled/loading states from `Button` for free
|
|
416
|
+
|
|
417
|
+
### When `Toggle` IS the right component
|
|
418
|
+
|
|
419
|
+
Reserve `Toggle` for its actual designed use case — **icon-only toolbar actions** with a binary on/off state:
|
|
420
|
+
|
|
421
|
+
- Bold / Italic / Underline in a rich-text toolbar
|
|
422
|
+
- Play / Pause
|
|
423
|
+
- Grid / List view
|
|
424
|
+
- Mute / Unmute
|
|
425
|
+
- Eye / Eye-slash (show/hide password)
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
<Toggle pressed={isBold} onPressedChange={setBold} aria-label="Bold">
|
|
429
|
+
<i className="ri-bold" />
|
|
430
|
+
</Toggle>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
The icon must fit within the square dimension — `[&_i]:text-[14px]` for size M, etc.
|
|
434
|
+
|
|
376
435
|
## Common Patterns
|
|
377
436
|
|
|
378
437
|
### Toolbar Group
|
package/docs/llms-manifest.json
CHANGED
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
{ "name": "Card", "version": "1.1.15", "documented": false },
|
|
65
65
|
{ "name": "CNLayout", "version": "1.1.15", "documented": false },
|
|
66
66
|
{ "name": "FieldSection", "version": "1.1.15", "documented": false },
|
|
67
|
-
{ "name": "
|
|
67
|
+
{ "name": "SectionBlock", "version": "1.1.22", "documented": true },
|
|
68
68
|
{ "name": "TreeSubLayout", "version": "1.1.15", "documented": false },
|
|
69
69
|
{ "name": "Divider", "version": "1.1.15", "documented": false },
|
|
70
70
|
{ "name": "ScrollArea", "version": "1.1.15", "documented": false }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torch-glare-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "MCP server for TORCH Glare component library — gives AI assistants full access to component docs, API references, code examples, and design system info",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|