@hex-core/components 1.5.0 → 1.7.0

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/dist/schemas.js CHANGED
@@ -1646,7 +1646,7 @@ var toggleGroupSchema = {
1646
1646
  "clsx",
1647
1647
  "tailwind-merge"
1648
1648
  ],
1649
- internal: ["toggle"],
1649
+ internal: ["primitives/toggle/toggle"],
1650
1650
  peer: ["react", "react-dom"]
1651
1651
  },
1652
1652
  tokensUsed: ["muted", "accent", "accent-foreground", "ring"],
@@ -1704,7 +1704,7 @@ var formSchema = {
1704
1704
  "clsx",
1705
1705
  "tailwind-merge"
1706
1706
  ],
1707
- internal: ["label"],
1707
+ internal: ["primitives/label/label"],
1708
1708
  peer: ["react", "react-dom"]
1709
1709
  },
1710
1710
  tokensUsed: ["destructive", "muted-foreground"],
@@ -4638,6 +4638,806 @@ var sidebarSchema = {
4638
4638
  tags: ["sidebar", "navigation", "app-shell", "layout"]
4639
4639
  };
4640
4640
 
4641
+ // src/primitives/empty/empty.schema.ts
4642
+ var emptySchema = {
4643
+ name: "empty",
4644
+ displayName: "Empty",
4645
+ description: "A zero-state surface for lists, dashboards, and search results with no content. Explains why the slot is empty + provides a call-to-action.",
4646
+ category: "primitive",
4647
+ subcategory: "feedback",
4648
+ props: [
4649
+ {
4650
+ name: "icon",
4651
+ type: "ReactNode",
4652
+ required: false,
4653
+ description: "Icon (typically an <svg>) rendered in a circular muted container above the title."
4654
+ },
4655
+ {
4656
+ name: "title",
4657
+ type: "ReactNode",
4658
+ required: true,
4659
+ description: "Heading copy. Becomes the region's accessible name via aria-labelledby."
4660
+ },
4661
+ {
4662
+ name: "description",
4663
+ type: "ReactNode",
4664
+ required: false,
4665
+ description: "Supporting copy that explains why the slot is empty + what to do next."
4666
+ },
4667
+ {
4668
+ name: "action",
4669
+ type: "ReactNode",
4670
+ required: false,
4671
+ description: "Call-to-action \u2014 typically a <Button> that creates the missing record."
4672
+ },
4673
+ {
4674
+ name: "titleAs",
4675
+ type: "enum",
4676
+ required: false,
4677
+ default: "h3",
4678
+ description: "Heading level for the title. Pick to match surrounding heading hierarchy.",
4679
+ enumValues: ["h2", "h3", "h4", "h5", "h6", "p"]
4680
+ },
4681
+ {
4682
+ name: "size",
4683
+ type: "enum",
4684
+ required: false,
4685
+ default: "default",
4686
+ description: "Visual scale.",
4687
+ enumValues: ["sm", "default", "lg"]
4688
+ }
4689
+ ],
4690
+ variants: [
4691
+ {
4692
+ name: "size",
4693
+ description: "Vertical density of the empty-state surface.",
4694
+ values: [
4695
+ {
4696
+ value: "sm",
4697
+ description: "Compact \u2014 fits inside a card / panel.",
4698
+ useWhen: "the empty surface is one of several stacked pieces of content; full-bleed would dominate"
4699
+ },
4700
+ {
4701
+ value: "default",
4702
+ description: "Standard \u2014 for table / list / dashboard widget empty states.",
4703
+ useWhen: "default everywhere; the only size you need on most surfaces"
4704
+ },
4705
+ {
4706
+ value: "lg",
4707
+ description: "Large \u2014 for full-page empty states (no items at all yet).",
4708
+ useWhen: "the empty surface owns the entire page \u2014 onboarding, first-run, no records yet"
4709
+ }
4710
+ ],
4711
+ default: "default"
4712
+ }
4713
+ ],
4714
+ slots: [
4715
+ {
4716
+ name: "icon",
4717
+ description: "Icon rendered in the circular muted container above the title.",
4718
+ required: false,
4719
+ acceptedTypes: ["ReactNode"]
4720
+ },
4721
+ {
4722
+ name: "action",
4723
+ description: "Call-to-action button under the description.",
4724
+ required: false,
4725
+ acceptedTypes: ["ReactNode"]
4726
+ }
4727
+ ],
4728
+ dependencies: {
4729
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
4730
+ internal: [],
4731
+ peer: ["react", "react-dom"]
4732
+ },
4733
+ tokensUsed: ["muted", "muted-foreground", "foreground", "border"],
4734
+ examples: [
4735
+ {
4736
+ title: "List zero-state",
4737
+ description: "Inbox with no messages \u2014 icon + title + description + compose button",
4738
+ code: '<Empty\n icon={<InboxIcon />}\n title="No messages yet"\n description="When someone sends a message, it shows up here."\n action={<Button>Compose</Button>}\n/>',
4739
+ composition: ["zero-state", "list", "with-action"]
4740
+ },
4741
+ {
4742
+ title: "Search zero-state",
4743
+ description: "Search returned zero results \u2014 show the query + suggest a reset",
4744
+ code: '<Empty\n icon={<SearchIcon />}\n title="No results found"\n description={`No items match "${query}". Try a different search.`}\n action={<Button variant="secondary" onClick={onReset}>Clear search</Button>}\n size="sm"\n/>',
4745
+ composition: ["zero-state", "search"]
4746
+ },
4747
+ {
4748
+ title: "Onboarding",
4749
+ description: "Full-page first-run state",
4750
+ code: '<Empty\n icon={<SparklesIcon />}\n title="Welcome to your dashboard"\n description="Create your first project to get started."\n action={<Button size="lg">Create project</Button>}\n size="lg"\n titleAs="h1"\n/>',
4751
+ composition: ["zero-state", "onboarding", "first-run"]
4752
+ }
4753
+ ],
4754
+ ai: {
4755
+ whenToUse: "Use for lists, search results, or dashboards where the request returned successfully but there's nothing to show. Explain why and what to do \u2014 the title names the absence, the description sets context, the action gives a way out.",
4756
+ whenNotToUse: "Don't use while data is still loading (use Loading). Don't use when something failed (use ErrorState). Don't use for inline form-field 'no options' messages (use Combobox's empty slot or Form's helper text).",
4757
+ commonMistakes: [
4758
+ "Using Empty without an action \u2014 leaves the user stuck",
4759
+ "Long descriptions \u2014 keep to one sentence; this is a state surface, not documentation",
4760
+ "Forgetting titleAs when the page already has an h2 above (heading hierarchy break)"
4761
+ ],
4762
+ antiPatterns: [
4763
+ {
4764
+ mistake: "Using Empty while data is still being fetched",
4765
+ insteadUse: "loading",
4766
+ why: "Empty announces 'no items exist'; Loading announces 'fetching items.' Showing Empty during the fetch flashes a misleading 'no results' state then snaps to real data on resolution."
4767
+ },
4768
+ {
4769
+ mistake: "Using Empty to display a fetch failure",
4770
+ insteadUse: "error-state",
4771
+ why: "ErrorState carries role='alert' so screen readers announce the failure on first render, AND it ships a retry affordance. Empty is silent on retry semantics."
4772
+ },
4773
+ {
4774
+ mistake: "Using Empty as an inline 'no matching options' notice inside a Combobox or Select",
4775
+ insteadUse: "combobox",
4776
+ why: "Combobox + Command already render a sub-AA-conformant empty state inside the listbox. Using Empty there leaks the page-level zero-state language into a focused interaction."
4777
+ }
4778
+ ],
4779
+ relatedComponents: ["loading", "error-state", "alert", "card", "skeleton"],
4780
+ accessibilityNotes: "Wrapped in a region landmark labeled by the title (aria-labelledby + auto-generated id). Icon is decorative (aria-hidden). Pair the action with a Button that has a visible label.",
4781
+ tokenBudget: 350
4782
+ },
4783
+ tags: ["empty", "zero-state", "feedback", "list", "dashboard"]
4784
+ };
4785
+
4786
+ // src/primitives/loading/loading.schema.ts
4787
+ var loadingSchema = {
4788
+ name: "loading",
4789
+ displayName: "Loading",
4790
+ description: "A composed loading-placeholder pattern for lists, cards, and stacks. Skeleton is the atom; Loading is the canonical molecule most surfaces want.",
4791
+ category: "primitive",
4792
+ subcategory: "feedback",
4793
+ props: [
4794
+ {
4795
+ name: "variant",
4796
+ type: "enum",
4797
+ required: false,
4798
+ default: "list",
4799
+ description: "Layout family \u2014 picks the row shape.",
4800
+ enumValues: ["list", "card", "stack"]
4801
+ },
4802
+ {
4803
+ name: "rows",
4804
+ type: "number",
4805
+ required: false,
4806
+ default: 3,
4807
+ description: "Number of placeholder rows to render. Each row may contain multiple Skeleton elements depending on variant \u2014 list emits 1 per row; card emits 3 (heading + 2 body lines); stack emits 3 (avatar + 2 text lines)."
4808
+ },
4809
+ {
4810
+ name: "label",
4811
+ type: "string",
4812
+ required: false,
4813
+ default: "Loading\u2026",
4814
+ description: "sr-only label announced by screen readers via aria-live polite."
4815
+ },
4816
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
4817
+ ],
4818
+ variants: [
4819
+ {
4820
+ name: "variant",
4821
+ description: "Layout family for the placeholder rows.",
4822
+ values: [
4823
+ {
4824
+ value: "list",
4825
+ description: "Single full-width line per row \u2014 table / list / sidebar nav loaders.",
4826
+ useWhen: "the surface being filled is a flat list of equally-shaped rows"
4827
+ },
4828
+ {
4829
+ value: "card",
4830
+ description: "Heading + two body lines inside a bordered card surface.",
4831
+ useWhen: "loading a Card-shaped widget (settings panel, dashboard tile)"
4832
+ },
4833
+ {
4834
+ value: "stack",
4835
+ description: "Avatar + two-line text block per row \u2014 message / contact / search-result loaders.",
4836
+ useWhen: "loading a list of avatar-paired items (chat history, contact list, comment thread)"
4837
+ }
4838
+ ],
4839
+ default: "list"
4840
+ }
4841
+ ],
4842
+ slots: [],
4843
+ dependencies: {
4844
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
4845
+ internal: ["primitives/skeleton/skeleton"],
4846
+ peer: ["react", "react-dom"]
4847
+ },
4848
+ tokensUsed: ["muted", "card", "border"],
4849
+ examples: [
4850
+ {
4851
+ title: "List loader",
4852
+ description: "5 placeholder rows for a dashboard table",
4853
+ code: '<Loading rows={5} variant="list" />',
4854
+ composition: ["loading", "table"]
4855
+ },
4856
+ {
4857
+ title: "Card loader",
4858
+ description: "Card-shaped placeholder with heading + 2 body lines",
4859
+ code: '<Loading variant="card" />',
4860
+ composition: ["loading", "card", "widget"]
4861
+ },
4862
+ {
4863
+ title: "Stack loader (chat / contacts)",
4864
+ description: "Avatar + 2-line text rows",
4865
+ code: '<Loading rows={4} variant="stack" />',
4866
+ composition: ["loading", "stack", "chat"]
4867
+ }
4868
+ ],
4869
+ ai: {
4870
+ whenToUse: "Use when fetching data for a list, card, or avatar-paired stack and you want pre-arranged placeholders. Pair the rows count with how many items you expect \u2014 avoid 10-row loaders for 2-item results (jarring on resolve).",
4871
+ whenNotToUse: "Don't use for a single shaped placeholder (use Skeleton). Don't use when the wait has a measurable percent-done (use Progress). Don't use for full-page transitions where a top progress bar is more informative.",
4872
+ commonMistakes: [
4873
+ "Forgetting `aria-live` would prevent screen-reader announcement \u2014 Loading already wires it; don't override role",
4874
+ "Using too many rows \u2014 flashes a heavy 'something's wrong' impression",
4875
+ "Applying Loading inside a Card AND also wrapping in another Card \u2014 double-bordered shimmer"
4876
+ ],
4877
+ antiPatterns: [
4878
+ {
4879
+ mistake: "Using Loading for a single shaped placeholder (avatar circle, heading line, image rectangle)",
4880
+ insteadUse: "skeleton",
4881
+ why: "Skeleton is the atom; render exactly one Skeleton with the right size class. Loading composes Skeletons for canonical multi-row patterns \u2014 no value if you only need one shape."
4882
+ },
4883
+ {
4884
+ mistake: "Using Loading when the wait has a measurable percent (file upload, batch import)",
4885
+ insteadUse: "progress",
4886
+ why: "Progress's `value` prop announces actual completion to assistive tech. Loading is for indeterminate waits."
4887
+ },
4888
+ {
4889
+ mistake: "Using Loading for a full-page route transition where the user just clicked a link",
4890
+ insteadUse: "skeleton",
4891
+ why: "Compose page-shaped Skeletons via `generateStaticParams` + Suspense; full-page Loading flashes 'work is happening here' while the right answer is 'show the page shell instantly.'"
4892
+ }
4893
+ ],
4894
+ relatedComponents: ["skeleton", "progress", "empty", "error-state"],
4895
+ accessibilityNotes: "role='status' + aria-live='polite' + sr-only label, so screen readers announce on first render. Caller doesn't need to wire any of this.",
4896
+ tokenBudget: 300
4897
+ },
4898
+ tags: ["loading", "skeleton", "placeholder", "feedback"]
4899
+ };
4900
+
4901
+ // src/primitives/error-state/error-state.schema.ts
4902
+ var errorStateSchema = {
4903
+ name: "error-state",
4904
+ displayName: "ErrorState",
4905
+ description: "A surface for failed-fetch / failed-action states. Visually mirrors Empty but with destructive bias and an optional retry button. Mounts with role='alert' so screen readers announce on first render.",
4906
+ category: "primitive",
4907
+ subcategory: "feedback",
4908
+ props: [
4909
+ { name: "icon", type: "ReactNode", required: false, description: "Icon (typically an alert / x-circle SVG)." },
4910
+ { name: "title", type: "ReactNode", required: false, default: "Something went wrong", description: "Heading copy. Falls back to a generic message." },
4911
+ { name: "message", type: "ReactNode", required: true, description: "Body copy explaining what failed and (optionally) what the user can do." },
4912
+ { name: "action", type: "ReactNode", required: false, description: "Call-to-action slot \u2014 typically a <Button onClick={refetch}>Retry</Button>. Consumer controls the button variant, loading state, and asChild composition." },
4913
+ {
4914
+ name: "variant",
4915
+ type: "enum",
4916
+ required: false,
4917
+ default: "default",
4918
+ description: "Visual tone \u2014 destructive draws more attention, default is calmer.",
4919
+ enumValues: ["default", "destructive"]
4920
+ }
4921
+ ],
4922
+ variants: [
4923
+ {
4924
+ name: "variant",
4925
+ description: "Tone of the surface.",
4926
+ values: [
4927
+ {
4928
+ value: "default",
4929
+ description: "Muted neutral surface \u2014 for recoverable / transient failures.",
4930
+ useWhen: "the failure is recoverable and you don't want to alarm: 'temporarily unavailable,' 'try again in a moment'"
4931
+ },
4932
+ {
4933
+ value: "destructive",
4934
+ description: "Tinted destructive surface \u2014 for hard / unrecoverable failures.",
4935
+ useWhen: "the failure is significant: data lost, permission denied, server error 5xx"
4936
+ }
4937
+ ],
4938
+ default: "default"
4939
+ }
4940
+ ],
4941
+ slots: [
4942
+ {
4943
+ name: "icon",
4944
+ description: "Icon rendered in the circular tinted container.",
4945
+ required: false,
4946
+ acceptedTypes: ["ReactNode"]
4947
+ },
4948
+ {
4949
+ name: "action",
4950
+ description: "Call-to-action \u2014 typically a Button. Consumer composes the button variant + click handler.",
4951
+ required: false,
4952
+ acceptedTypes: ["ReactNode"]
4953
+ }
4954
+ ],
4955
+ dependencies: {
4956
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
4957
+ internal: [],
4958
+ peer: ["react", "react-dom"]
4959
+ },
4960
+ tokensUsed: ["destructive", "muted", "muted-foreground", "foreground", "border"],
4961
+ examples: [
4962
+ {
4963
+ title: "Fetch failure with retry",
4964
+ description: "Couldn't load messages \u2014 show the error + a Retry button in the action slot",
4965
+ code: `<ErrorState
4966
+ icon={<AlertCircleIcon />}
4967
+ title="Couldn't load messages"
4968
+ message="The server didn't respond. Check your connection and try again."
4969
+ action={<Button onClick={() => refetch()}>Retry</Button>}
4970
+ />`,
4971
+ composition: ["error-state", "retry", "fetch-failure"]
4972
+ },
4973
+ {
4974
+ title: "Hard failure (destructive)",
4975
+ description: "Server returned 5xx and we want to alarm",
4976
+ code: `<ErrorState
4977
+ icon={<XCircleIcon />}
4978
+ variant="destructive"
4979
+ title="Sync failed"
4980
+ message="We couldn't save your changes. They're still on this device \u2014 refresh to retry."
4981
+ />`,
4982
+ composition: ["error-state", "destructive", "sync-failure"]
4983
+ }
4984
+ ],
4985
+ ai: {
4986
+ whenToUse: "Use for any failure surface where the user might benefit from context + a retry path. Pair with onRetry whenever the operation is genuinely retryable; omit it when the user has to take a different action (e.g. log in again).",
4987
+ whenNotToUse: "Don't use for inline form-field errors (Form's <FormMessage> handles those). Don't use for blocking destructive confirmations (AlertDialog). Don't use for transient toasts (Sonner / Toaster).",
4988
+ commonMistakes: [
4989
+ "Generic 'Something went wrong' message without context \u2014 at minimum, name what failed",
4990
+ "Using destructive variant for recoverable network blips (cries wolf)",
4991
+ "Wiring the action's click handler to a function that doesn't actually re-trigger the failed operation",
4992
+ "Forgetting the action slot when the failure IS retryable \u2014 leaves the user stuck"
4993
+ ],
4994
+ antiPatterns: [
4995
+ {
4996
+ mistake: "Using ErrorState for an inline form-field validation error",
4997
+ insteadUse: "form",
4998
+ why: "Form ships <FormMessage> which is wired to the field's aria-describedby. ErrorState announces page-level failures; using it inline duplicates the role='alert' on every keystroke and confuses assistive tech."
4999
+ },
5000
+ {
5001
+ mistake: "Using ErrorState as a blocking destructive confirmation modal",
5002
+ insteadUse: "alert-dialog",
5003
+ why: "AlertDialog is role='alertdialog' (modal, focus-trapped, dismiss-on-Escape). ErrorState is a passive surface \u2014 no interaction guarantees. If you need the user to confirm before proceeding, use AlertDialog."
5004
+ },
5005
+ {
5006
+ mistake: "Using ErrorState for a transient toast notification",
5007
+ insteadUse: "sonner",
5008
+ why: "Sonner / Toaster auto-dismiss + stack + animate. ErrorState sits in the layout. For 'fire-and-forget failure ack,' use the toaster."
5009
+ }
5010
+ ],
5011
+ relatedComponents: ["alert", "alert-dialog", "form", "empty", "loading", "sonner"],
5012
+ accessibilityNotes: "role='alert' so screen readers announce on first render. Icon decorative (aria-hidden). Retry button uses focus-visible ring; pressing it doesn't dismiss the surface \u2014 caller controls that via state.",
5013
+ tokenBudget: 450
5014
+ },
5015
+ tags: ["error", "error-state", "failure", "retry", "feedback"]
5016
+ };
5017
+
5018
+ // src/primitives/tag/tag.schema.ts
5019
+ var tagSchema = {
5020
+ name: "tag",
5021
+ displayName: "Tag",
5022
+ description: "Interactive tag / chip primitive \u2014 Badge with an optional dismiss button. Mirrors Badge's variants so the visual sibling is obvious; ships with a close affordance for filter pills, multi-select selections, draft attachments.",
5023
+ category: "primitive",
5024
+ subcategory: "feedback",
5025
+ props: [
5026
+ {
5027
+ name: "variant",
5028
+ type: "enum",
5029
+ required: false,
5030
+ default: "default",
5031
+ description: "Visual style.",
5032
+ enumValues: ["default", "secondary", "destructive", "outline"]
5033
+ },
5034
+ {
5035
+ name: "icon",
5036
+ type: "ReactNode",
5037
+ required: false,
5038
+ description: "Optional leading icon (sized 12x12 inside the tag)."
5039
+ },
5040
+ {
5041
+ name: "onRemove",
5042
+ type: "function",
5043
+ required: false,
5044
+ description: "Click handler for the close button. When provided, the dismiss \u2715 is rendered. Signature: () => void."
5045
+ },
5046
+ {
5047
+ name: "removeLabel",
5048
+ type: "string",
5049
+ required: false,
5050
+ description: "Override the auto-derived close-button aria-label (default: 'Remove ${children}')."
5051
+ },
5052
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
5053
+ ],
5054
+ variants: [
5055
+ {
5056
+ name: "variant",
5057
+ description: "Visual style \u2014 mirrors Badge so users get consistent token use.",
5058
+ values: [
5059
+ {
5060
+ value: "default",
5061
+ description: "Primary-tinted filled tag.",
5062
+ useWhen: "the tag represents an active selected state \u2014 applied filter, picked option"
5063
+ },
5064
+ {
5065
+ value: "secondary",
5066
+ description: "Muted neutral filled tag with a hairline border.",
5067
+ useWhen: "default for everyday tags \u2014 list of selected items, draft attachments, filter pills"
5068
+ },
5069
+ {
5070
+ value: "destructive",
5071
+ description: "Red-tinted filled tag.",
5072
+ useWhen: "the tag represents a flagged / problematic value \u2014 banned user, archived item"
5073
+ },
5074
+ {
5075
+ value: "outline",
5076
+ description: "Bordered transparent tag.",
5077
+ useWhen: "the tag lives on a busy surface and shouldn't add visual fill weight"
5078
+ }
5079
+ ],
5080
+ default: "default"
5081
+ }
5082
+ ],
5083
+ slots: [
5084
+ {
5085
+ name: "icon",
5086
+ description: "Leading icon (sized 12x12).",
5087
+ required: false,
5088
+ acceptedTypes: ["ReactNode"]
5089
+ },
5090
+ {
5091
+ name: "children",
5092
+ description: "Tag label content (string preferred for auto aria-label derivation).",
5093
+ required: true,
5094
+ acceptedTypes: ["ReactNode"]
5095
+ }
5096
+ ],
5097
+ dependencies: {
5098
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
5099
+ internal: [],
5100
+ peer: ["react", "react-dom"]
5101
+ },
5102
+ tokensUsed: ["primary", "primary-foreground", "secondary", "secondary-foreground", "destructive", "destructive-foreground", "foreground", "ring"],
5103
+ examples: [
5104
+ {
5105
+ title: "Filter pill with dismiss",
5106
+ description: "Removable filter chip in a search bar",
5107
+ code: '<Tag variant="secondary" onRemove={() => removeFilter("urgent")}>Urgent</Tag>',
5108
+ composition: ["filter", "search", "removable"]
5109
+ },
5110
+ {
5111
+ title: "Multi-select selections",
5112
+ description: "Stack of selected values with per-tag dismiss",
5113
+ code: '<div className="flex flex-wrap gap-2">\n {selected.map((tag) => (\n <Tag key={tag} variant="secondary" onRemove={() => deselect(tag)}>\n {tag}\n </Tag>\n ))}\n</div>',
5114
+ composition: ["multi-select", "form", "removable"]
5115
+ },
5116
+ {
5117
+ title: "Status tag (non-interactive)",
5118
+ description: "Static tag without dismiss \u2014 equivalent to Badge but uses Tag for consistency",
5119
+ code: '<Tag variant="outline">Beta</Tag>',
5120
+ composition: ["status", "label"]
5121
+ }
5122
+ ],
5123
+ ai: {
5124
+ whenToUse: "Use for tokens the user can dismiss: filter pills, multi-select selections, draft attachments. Pair onRemove with a state setter that drops the value from your collection.",
5125
+ whenNotToUse: "Don't use for non-interactive labels (use Badge). Don't use for state-bearing 'click to filter' affordances \u2014 those should be Toggle or ToggleGroup so the active state is announced as 'pressed'/'not pressed' to assistive tech.",
5126
+ commonMistakes: [
5127
+ "Forgetting onRemove on a tag the user CAN dismiss \u2014 leaves the affordance invisible",
5128
+ "Calling it Tag but rendering inside a list of static labels \u2014 confuses Tag (interactive) vs Badge (decorative) semantics",
5129
+ "Wrapping the tag itself in a <button> \u2014 the dismiss button already handles its own click; double-button breaks accessibility"
5130
+ ],
5131
+ antiPatterns: [
5132
+ {
5133
+ mistake: "Using Tag without onRemove for a static / decorative label",
5134
+ insteadUse: "badge",
5135
+ why: "Badge is the non-interactive sibling. Tag without onRemove ships an unused close-button container \u2014 wasted DOM and ambiguous semantics. Reach for Badge when there's nothing to dismiss."
5136
+ },
5137
+ {
5138
+ mistake: "Using Tag as a click-to-filter chip (selected vs unselected state)",
5139
+ insteadUse: "toggle",
5140
+ why: "Toggle's role='button' + aria-pressed announces selected state to AT. Tag's close-button doesn't model 'pressed'; consumers end up with non-announced state."
5141
+ },
5142
+ {
5143
+ mistake: "Using Tag for a single-select dropdown's chosen value",
5144
+ insteadUse: "select",
5145
+ why: "Select renders its value text inside the trigger. Tag would duplicate the token and complicate the keyboard nav (focus on Tag's close vs Select's trigger)."
5146
+ }
5147
+ ],
5148
+ relatedComponents: ["badge", "toggle", "toggle-group", "multi-combobox"],
5149
+ accessibilityNotes: "Close button gets aria-label derived from children when they're a string ('Remove Urgent'). Override via removeLabel. Tag itself is a span \u2014 wrap in a list (`role='list'` + `role='listitem'`) when rendering N tags as a collection.",
5150
+ tokenBudget: 350
5151
+ },
5152
+ tags: ["tag", "chip", "pill", "filter", "removable", "multi-select"]
5153
+ };
5154
+
5155
+ // src/components/tree/tree.schema.ts
5156
+ var treeSchema = {
5157
+ name: "tree",
5158
+ displayName: "Tree",
5159
+ description: "Generic hierarchical list with roving-tabindex keyboard navigation. Distinct from FileTree (which adds folder/file icon-by-extension logic) \u2014 Tree is content-agnostic for org charts, taxonomy pickers, navigation trees.",
5160
+ category: "component",
5161
+ subcategory: "navigation",
5162
+ props: [
5163
+ { name: "data", type: "object", required: true, description: "Root nodes (TreeNode[])." },
5164
+ { name: "defaultExpanded", type: "object", required: false, description: "Initial expanded ids (uncontrolled). string[]." },
5165
+ { name: "expanded", type: "object", required: false, description: "Controlled expanded ids. string[]." },
5166
+ { name: "onExpandedChange", type: "function", required: false, description: "Callback (ids: string[]) => void." },
5167
+ { name: "selected", type: "string", required: false, description: "Controlled selected node id." },
5168
+ { name: "onSelect", type: "function", required: false, description: "Callback (id: string) => void on activation." },
5169
+ { name: "aria-label", type: "string", required: true, description: "Accessible name for the tree landmark." },
5170
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
5171
+ ],
5172
+ variants: [],
5173
+ slots: [],
5174
+ dependencies: {
5175
+ npm: ["clsx", "tailwind-merge"],
5176
+ internal: [],
5177
+ peer: ["react", "react-dom"]
5178
+ },
5179
+ tokensUsed: ["foreground", "accent", "accent-foreground", "ring"],
5180
+ examples: [
5181
+ {
5182
+ title: "Org chart",
5183
+ description: "Hierarchical staff tree with expand/select",
5184
+ code: '<Tree\n aria-label="Org chart"\n data={[\n { id: "ceo", label: "CEO", children: [\n { id: "cto", label: "CTO", children: [{ id: "eng-lead", label: "Eng Lead" }] },\n { id: "cmo", label: "CMO" },\n ]},\n ]}\n defaultExpanded={["ceo"]}\n onSelect={(id) => console.log(id)}\n/>',
5185
+ composition: ["tree", "navigation", "hierarchy"]
5186
+ },
5187
+ {
5188
+ title: "Taxonomy picker",
5189
+ description: "Categorize a record by picking a leaf in a 3-level taxonomy",
5190
+ code: '<Tree\n aria-label="Category"\n data={taxonomy}\n selected={category}\n onSelect={setCategory}\n/>',
5191
+ composition: ["tree", "form", "picker"]
5192
+ }
5193
+ ],
5194
+ ai: {
5195
+ whenToUse: "Use for hierarchical data the user navigates by keyboard or click \u2014 org charts, taxonomy pickers, generic nav trees. Pair with selected + onSelect for picker semantics; pair with onExpandedChange to control expansion externally.",
5196
+ whenNotToUse: "Don't use for filesystem-shaped data \u2014 FileTree adds folder/file icon-by-extension. Don't use for two-level groupings without item-selection \u2014 Accordion is the cleaner abstraction. Don't use for primary site nav (NavigationMenu).",
5197
+ commonMistakes: [
5198
+ "Forgetting aria-label \u2014 required, no fallback",
5199
+ "Confusing data prop (TreeNode[]) with FileTree's nodes prop (FileTreeNode[]) \u2014 same shape but different icon defaults",
5200
+ "Trying to pass selected without onSelect \u2014 controlled-mode without an updater leaves it stuck"
5201
+ ],
5202
+ antiPatterns: [
5203
+ {
5204
+ mistake: "Using Tree for filesystem-shaped data (folders + files with icon-by-extension)",
5205
+ insteadUse: "file-tree",
5206
+ why: "FileTree ships file/folder icon defaults + extension-based color hints. Tree is icon-agnostic \u2014 using it for files means hand-wiring icon logic each time."
5207
+ },
5208
+ {
5209
+ mistake: "Using Tree for a two-level grouping (sections that expand to reveal content) without item-selection",
5210
+ insteadUse: "accordion",
5211
+ why: "Accordion is purpose-built for collapse-content-into-sections. Tree's role='tree' implies item-selection semantics that don't fit pure expand/collapse content groups."
5212
+ },
5213
+ {
5214
+ mistake: "Using Tree for primary site / app navigation",
5215
+ insteadUse: "navigation-menu",
5216
+ why: "NavigationMenu ships landmark role + skip links + the right ARIA for primary nav. Tree's tree role is for data, not routing."
5217
+ }
5218
+ ],
5219
+ relatedComponents: ["file-tree", "accordion", "navigation-menu"],
5220
+ accessibilityNotes: "role='tree' on root, role='treeitem' per row, aria-expanded on parents, aria-selected on the chosen leaf. Keyboard: \u2191\u2193 move focus, \u2192 expand (or jump to first child if expanded), \u2190 collapse (or jump to parent), Home/End first/last visible row, Enter/Space activate.",
5221
+ tokenBudget: 700
5222
+ },
5223
+ tags: ["tree", "hierarchy", "navigation", "picker", "ARIA-tree"]
5224
+ };
5225
+
5226
+ // src/components/toolbar/toolbar.schema.ts
5227
+ var toolbarSchema = {
5228
+ name: "toolbar",
5229
+ displayName: "Toolbar",
5230
+ description: "A horizontal or vertical group of related controls (buttons, toggles, links, separators) with arrow-key roving focus. Wraps Radix Toolbar so the keyboard semantics are correct out of the box.",
5231
+ category: "component",
5232
+ subcategory: "navigation",
5233
+ props: [
5234
+ {
5235
+ name: "orientation",
5236
+ type: "enum",
5237
+ required: false,
5238
+ default: "horizontal",
5239
+ description: "Layout direction. Drives arrow-key behavior \u2014 \u2190\u2192 for horizontal, \u2191\u2193 for vertical.",
5240
+ enumValues: ["horizontal", "vertical"]
5241
+ },
5242
+ {
5243
+ name: "aria-label",
5244
+ type: "string",
5245
+ required: true,
5246
+ description: "Accessible name for the toolbar landmark. Toolbar mounts as a role='toolbar' region \u2014 without a name, AT users hear an unlabelled landmark. If a visible heading sits adjacent, you may pair via aria-labelledby instead (which still satisfies this requirement)."
5247
+ }
5248
+ ],
5249
+ variants: [
5250
+ {
5251
+ name: "orientation",
5252
+ description: "Layout direction.",
5253
+ values: [
5254
+ {
5255
+ value: "horizontal",
5256
+ description: "Default \u2014 controls flow left-to-right.",
5257
+ useWhen: "the toolbar sits at the top or bottom of a panel / editor"
5258
+ },
5259
+ {
5260
+ value: "vertical",
5261
+ description: "Controls flow top-to-bottom.",
5262
+ useWhen: "the toolbar runs alongside a canvas / editor as a side rail"
5263
+ }
5264
+ ],
5265
+ default: "horizontal"
5266
+ }
5267
+ ],
5268
+ slots: [
5269
+ {
5270
+ name: "children",
5271
+ description: "Mix of ToolbarButton / ToolbarToggleGroup / ToolbarToggleItem / ToolbarLink / ToolbarSeparator.",
5272
+ required: true,
5273
+ acceptedTypes: ["ReactNode"]
5274
+ }
5275
+ ],
5276
+ dependencies: {
5277
+ npm: ["@radix-ui/react-toolbar", "class-variance-authority", "clsx", "tailwind-merge"],
5278
+ internal: [],
5279
+ peer: ["react", "react-dom"]
5280
+ },
5281
+ tokensUsed: ["card", "border", "foreground", "accent", "accent-foreground", "ring"],
5282
+ examples: [
5283
+ {
5284
+ title: "Editor toolbar with toggle group",
5285
+ description: "Undo + Redo + Separator + alignment toggles",
5286
+ code: '<Toolbar aria-label="Editor controls">\n <ToolbarButton onClick={onUndo}>Undo</ToolbarButton>\n <ToolbarButton onClick={onRedo}>Redo</ToolbarButton>\n <ToolbarSeparator />\n <ToolbarToggleGroup type="single" defaultValue="left">\n <ToolbarToggleItem value="left">Left</ToolbarToggleItem>\n <ToolbarToggleItem value="center">Center</ToolbarToggleItem>\n <ToolbarToggleItem value="right">Right</ToolbarToggleItem>\n </ToolbarToggleGroup>\n</Toolbar>',
5287
+ composition: ["toolbar", "editor", "toggle-group"]
5288
+ },
5289
+ {
5290
+ title: "Side-rail vertical toolbar",
5291
+ description: "Vertical toolbar alongside a canvas",
5292
+ code: '<Toolbar orientation="vertical" aria-label="Drawing tools">\n <ToolbarToggleGroup type="single" defaultValue="select">\n <ToolbarToggleItem value="select"><CursorIcon /></ToolbarToggleItem>\n <ToolbarToggleItem value="pen"><PenIcon /></ToolbarToggleItem>\n <ToolbarToggleItem value="eraser"><EraserIcon /></ToolbarToggleItem>\n </ToolbarToggleGroup>\n <ToolbarSeparator />\n <ToolbarButton aria-label="Settings"><SettingsIcon /></ToolbarButton>\n</Toolbar>',
5293
+ composition: ["toolbar", "vertical", "canvas", "icon-only"]
5294
+ }
5295
+ ],
5296
+ ai: {
5297
+ whenToUse: "Use to group related controls that operate on a single content area (editor, canvas, table). The roving-tabindex behavior means Tab enters the toolbar at the first item, then arrow keys navigate within \u2014 preserves the surrounding page's tab order.",
5298
+ whenNotToUse: "Don't use for app-level menus (use Menubar). Don't use for a single mutually-exclusive selection (use ToggleGroup directly \u2014 Toolbar is for groupING ToggleGroups + Buttons + Links). Don't use for navigation (use NavigationMenu).",
5299
+ commonMistakes: [
5300
+ "Forgetting aria-label when the toolbar isn't adjacent to a visible heading",
5301
+ "Mixing controls that operate on different content areas \u2014 Toolbar implies a single target",
5302
+ "Using Toolbar inside a Toolbar \u2014 nest a vertical Toolbar inside a horizontal one if you really need a 2D grid, but consider whether a single Toolbar with Separators is clearer"
5303
+ ],
5304
+ antiPatterns: [
5305
+ {
5306
+ mistake: "Using Toolbar for a single-axis mutually-exclusive selection (alignment, list-style)",
5307
+ insteadUse: "toggle-group",
5308
+ why: "Toolbar is the multi-control container. ToggleGroup directly gives you 'pick one of N' with proper keyboard semantics \u2014 adding a Toolbar wrapper just costs DOM."
5309
+ },
5310
+ {
5311
+ mistake: "Using Toolbar for an application top-level menu (File, Edit, View, \u2026)",
5312
+ insteadUse: "menubar",
5313
+ why: "Menubar models nested menus with proper open-on-focus semantics. Toolbar is for flat command groups; the menu pattern needs Menubar."
5314
+ },
5315
+ {
5316
+ mistake: "Using Toolbar for primary site / app navigation",
5317
+ insteadUse: "navigation-menu",
5318
+ why: "NavigationMenu ships landmark role + skip links + the right ARIA attributes for primary nav. Toolbar is for content-region controls, not page-level routing."
5319
+ }
5320
+ ],
5321
+ relatedComponents: ["toggle", "toggle-group", "menubar", "navigation-menu", "separator"],
5322
+ accessibilityNotes: "Radix wires role='toolbar', roving tabindex (Tab enters at the first focusable item, arrows move within), Home/End jump to first/last. Always pass aria-label when there's no visible heading next to the toolbar.",
5323
+ tokenBudget: 600
5324
+ },
5325
+ tags: ["toolbar", "editor", "controls", "command-group"]
5326
+ };
5327
+
5328
+ // src/ai/attachment/attachment.schema.ts
5329
+ var attachmentSchema = {
5330
+ name: "attachment",
5331
+ displayName: "Attachment",
5332
+ description: "File / image thumbnail with optional remove affordance + upload-progress overlay. Composes with the AI Composer for multimodal message drafts. Auto-detects image vs file variant from MIME type + preview URL.",
5333
+ category: "ai",
5334
+ subcategory: "input",
5335
+ props: [
5336
+ {
5337
+ name: "file",
5338
+ type: "object",
5339
+ required: true,
5340
+ description: "Native File OR { name, size, type, preview? } metadata. Drives variant auto-detection (image+preview \u2192 thumbnail; else file icon + name + size)."
5341
+ },
5342
+ {
5343
+ name: "onRemove",
5344
+ type: "function",
5345
+ required: false,
5346
+ description: "Click handler for the \xD7 overlay button. Signature: () => void."
5347
+ },
5348
+ {
5349
+ name: "progress",
5350
+ type: "number",
5351
+ required: false,
5352
+ description: "Upload progress in [0, 1). Values >= 1 (or undefined) hide the progressbar \u2014 pass undefined once the upload completes. Internally scaled to 0\u2013100 for aria-valuenow so AT announces 'NN percent.'"
5353
+ },
5354
+ {
5355
+ name: "variant",
5356
+ type: "enum",
5357
+ required: false,
5358
+ description: "Override the auto-detected variant. 'image' renders a thumbnail; 'file' renders icon + name + size.",
5359
+ enumValues: ["file", "image"]
5360
+ },
5361
+ { name: "className", type: "string", required: false, description: "Additional CSS classes" }
5362
+ ],
5363
+ variants: [
5364
+ {
5365
+ name: "variant",
5366
+ description: "Visual layout. Auto-detected unless overridden.",
5367
+ values: [
5368
+ {
5369
+ value: "file",
5370
+ description: "Generic file icon + name + size. Bordered card with horizontal padding.",
5371
+ useWhen: "the attachment is a non-image (PDF, code, archive, audio) OR an image without a preview URL"
5372
+ },
5373
+ {
5374
+ value: "image",
5375
+ description: "80\xD780 thumbnail of the preview URL. Zero padding so the image fills the card.",
5376
+ useWhen: "the attachment is an image AND a preview URL is available (use URL.createObjectURL on the File before passing in)"
5377
+ }
5378
+ ],
5379
+ default: "file"
5380
+ }
5381
+ ],
5382
+ slots: [],
5383
+ dependencies: {
5384
+ npm: ["class-variance-authority", "clsx", "tailwind-merge"],
5385
+ internal: [],
5386
+ peer: ["react", "react-dom"]
5387
+ },
5388
+ tokensUsed: ["card", "border", "foreground", "muted", "muted-foreground", "primary", "accent", "ring"],
5389
+ examples: [
5390
+ {
5391
+ title: "Image preview in composer",
5392
+ description: "Image attachment with remove affordance",
5393
+ code: "function ComposerWithImage() {\n const [file, setFile] = React.useState<File | null>(null);\n const preview = file ? URL.createObjectURL(file) : null;\n return (\n <Composer>\n {file && preview ? (\n <Attachment\n file={{ name: file.name, size: file.size, type: file.type, preview }}\n onRemove={() => setFile(null)}\n />\n ) : null}\n {/* \u2026textarea + send button\u2026 */}\n </Composer>\n );\n}",
5394
+ composition: ["composer", "ai", "image", "removable"]
5395
+ },
5396
+ {
5397
+ title: "Upload progress",
5398
+ description: "File attachment with live progress overlay",
5399
+ code: '<Attachment\n file={{ name: "report.pdf", size: 2_400_000, type: "application/pdf" }}\n progress={uploadProgress}\n onRemove={() => cancelUpload()}\n/>',
5400
+ composition: ["upload", "progress", "removable"]
5401
+ },
5402
+ {
5403
+ title: "Static file preview (no remove)",
5404
+ description: "Read-only attachment in a sent message",
5405
+ code: '<Attachment file={{ name: "diagram.svg", size: 18000, type: "image/svg+xml" }} />',
5406
+ composition: ["read-only", "message"]
5407
+ }
5408
+ ],
5409
+ ai: {
5410
+ whenToUse: "Use inside a Composer to show pending attachments the user can remove before sending, or inside a Message to show attached files in a sent turn. Pair native File objects with URL.createObjectURL(file) before rendering \u2014 the component doesn't manage object URLs.",
5411
+ whenNotToUse: "Don't use for the upload-target affordance itself (that's Dropzone \u2014 it handles drag-drop + click-to-pick). Don't use for static document previews outside a chat composer (use Card with a file icon). Don't use for inline images in markdown \u2014 use Markdown's image rendering.",
5412
+ commonMistakes: [
5413
+ "Forgetting URL.createObjectURL on a native File \u2014 preview won't render and the variant falls back to file",
5414
+ "Not revoking object URLs when the attachment is removed \u2014 leaks memory",
5415
+ "Using Attachment without onRemove inside a Composer \u2014 leaves the user unable to remove"
5416
+ ],
5417
+ antiPatterns: [
5418
+ {
5419
+ mistake: "Using Attachment as the upload-input affordance (drag-drop area, click-to-pick)",
5420
+ insteadUse: "dropzone",
5421
+ why: "Dropzone is purpose-built for the upload-target \u2014 drag/drop semantics, click-to-pick, accept-MIME validation. Attachment SHOWS the result; Dropzone is the input."
5422
+ },
5423
+ {
5424
+ mistake: "Using Attachment for static document previews on a profile / details page",
5425
+ insteadUse: "card",
5426
+ why: "Card is the static-content surface. Attachment ships remove + progress overlays consumers don't need outside the chat-composer flow."
5427
+ },
5428
+ {
5429
+ mistake: "Using Attachment to render an inline image inside markdown body text",
5430
+ insteadUse: "markdown",
5431
+ why: "Markdown's `![alt](url)` already renders an inline image with the right semantics. Attachment is for the discrete-attachment-token UX (with name, size, remove); markdown handles inline media."
5432
+ }
5433
+ ],
5434
+ relatedComponents: ["composer", "dropzone", "message", "message-list"],
5435
+ accessibilityNotes: "Image variant uses <img alt> from the file name. Remove button gets aria-label='Remove ${name}'. Progress overlay is role='progressbar' with aria-valuemin=0 / aria-valuemax=100 / aria-valuenow=Math.round(progress * 100) and a generic 'Uploading ${name}' aria-label \u2014 AT announces 'NN percent.'",
5436
+ tokenBudget: 500
5437
+ },
5438
+ tags: ["attachment", "ai", "composer", "file", "image", "upload", "multimodal"]
5439
+ };
5440
+
4641
5441
  // src/ai/message/message.schema.ts
4642
5442
  var messageSchema = {
4643
5443
  name: "message",
@@ -5439,7 +6239,7 @@ var codeBlockSchema = {
5439
6239
  name: "themes",
5440
6240
  type: "object",
5441
6241
  required: false,
5442
- default: { light: "github-light", dark: "github-dark" },
6242
+ default: { light: "github-light-high-contrast", dark: "github-dark" },
5443
6243
  description: "Override the default theme pair. Keys: `light`, `dark` \u2014 values are Shiki theme IDs."
5444
6244
  },
5445
6245
  {
@@ -5486,6 +6286,6 @@ var codeBlockSchema = {
5486
6286
  tags: ["ai", "code", "shiki", "highlight", "copy", "rsc"]
5487
6287
  };
5488
6288
 
5489
- export { accordionSchema, alertDialogSchema, alertSchema, aspectRatioSchema, avatarSchema, badgeSchema, breadcrumbSchema, buttonSchema, calendarSchema, cardSchema, checkboxSchema, citationSchema, clusterSchema, codeBlockSchema, collapsibleSchema, colorPickerSchema, comboboxSchema, commandSchema, composerSchema, containerSchema, contextMenuSchema, dataTableSchema, datePickerSchema, dialogSchema, drawerSchema, dropdownMenuSchema, dropzoneSchema, fileTreeSchema, formSchema, gridSchema, hoverCardSchema, inputOTPSchema, inputSchema, labelSchema, loadingIndicatorSchema, markdownSchema, menubarSchema, messageActionsSchema, messageListSchema, messageSchema, multiComboboxSchema, navigationMenuSchema, paginationSchema, popoverSchema, progressSchema, radioGroupSchema, reasoningSchema, resizableSchema, scrollAreaSchema, selectSchema, separatorSchema, sheetSchema, sidebarSchema, skeletonSchema, sliderSchema, sonnerSchema, spacerSchema, stackSchema, stepperSchema, suggestionSchema, switchSchema, tableSchema, tabsSchema, textareaSchema, timePickerSchema, timelineSchema, toggleGroupSchema, toggleSchema, toolCallSchema, tooltipSchema };
6289
+ export { accordionSchema, alertDialogSchema, alertSchema, aspectRatioSchema, attachmentSchema, avatarSchema, badgeSchema, breadcrumbSchema, buttonSchema, calendarSchema, cardSchema, checkboxSchema, citationSchema, clusterSchema, codeBlockSchema, collapsibleSchema, colorPickerSchema, comboboxSchema, commandSchema, composerSchema, containerSchema, contextMenuSchema, dataTableSchema, datePickerSchema, dialogSchema, drawerSchema, dropdownMenuSchema, dropzoneSchema, emptySchema, errorStateSchema, fileTreeSchema, formSchema, gridSchema, hoverCardSchema, inputOTPSchema, inputSchema, labelSchema, loadingIndicatorSchema, loadingSchema, markdownSchema, menubarSchema, messageActionsSchema, messageListSchema, messageSchema, multiComboboxSchema, navigationMenuSchema, paginationSchema, popoverSchema, progressSchema, radioGroupSchema, reasoningSchema, resizableSchema, scrollAreaSchema, selectSchema, separatorSchema, sheetSchema, sidebarSchema, skeletonSchema, sliderSchema, sonnerSchema, spacerSchema, stackSchema, stepperSchema, suggestionSchema, switchSchema, tableSchema, tabsSchema, tagSchema, textareaSchema, timePickerSchema, timelineSchema, toggleGroupSchema, toggleSchema, toolCallSchema, toolbarSchema, tooltipSchema, treeSchema };
5490
6290
  //# sourceMappingURL=schemas.js.map
5491
6291
  //# sourceMappingURL=schemas.js.map