torch-glare-mcp 1.0.4 → 1.0.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.
@@ -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
- ### `DialogContent` ships without background, padding, border, or sizing
487
+ ### Every Dialog subcomponent ships without panel/typography defaults
488
488
 
489
- The base classes on the shipped `DialogContent` are missing every property a usable dialog panel needs — no background color (transparent), no padding (form fields flush against the edge), no border / rounded corners, `w-fit` (so the panel sizes to content), and no `max-height` / overflow handling. Out of the box, dialogs are visually broken and tall forms get cut off.
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
- **Override pattern** — wrap or replace the shipped `DialogContent` with these defaults so you don't have to repeat them on every consumer:
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
- <DialogPrimitive.Portal>
502
- <DialogPrimitive.Overlay className="fixed inset-0 z-50 bg-black/80" />
531
+ <DialogPortal>
532
+ <DialogOverlay />
503
533
  <DialogPrimitive.Content
504
534
  ref={ref}
505
535
  className={cn(
506
- "fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%]",
507
- "flex flex-col gap-4 p-6 rounded-2xl",
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
- "bg-background-presentation-form-base",
510
- "shadow-2xl",
511
- "max-h-[90vh] overflow-y-auto",
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
- </DialogPrimitive.Portal>
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 defaults:
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
- - `bg-background-presentation-form-base` — solid background using the design token
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>` lacks visual hierarchy. Standard pattern across production dialogs:
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>
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torch-glare-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
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",