@mohasinac/appkit 4.27.0 → 4.28.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.
Files changed (271) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
  3. package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
  4. package/dist/_internal/server/features/checkout/actions.js +6 -4
  5. package/dist/_internal/server/features/classified/metadata.js +3 -1
  6. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  7. package/dist/_internal/server/features/live/metadata.js +3 -1
  8. package/dist/_internal/server/features/refunds/actions.js +3 -2
  9. package/dist/_internal/server/features/reviews/actions.js +3 -2
  10. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  11. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  12. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  13. package/dist/_internal/shared/actions/action-registry.js +14 -0
  14. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  15. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  16. package/dist/_internal/shared/features/checkout/config.js +6 -3
  17. package/dist/client.d.ts +32 -2
  18. package/dist/client.js +37 -2
  19. package/dist/constants/api-endpoints.d.ts +21 -3
  20. package/dist/constants/api-endpoints.js +14 -1
  21. package/dist/constants/field-names.d.ts +2 -2
  22. package/dist/constants/field-names.js +2 -2
  23. package/dist/core/newsletter.repository.js +14 -1
  24. package/dist/features/about/components/FAQPageView.js +1 -1
  25. package/dist/features/about/components/FeesView.js +1 -1
  26. package/dist/features/account/components/AddressForm.d.ts +2 -0
  27. package/dist/features/account/components/AddressForm.js +5 -1
  28. package/dist/features/account/components/UserBidsView.js +1 -1
  29. package/dist/features/account/components/UserSidebar.js +1 -1
  30. package/dist/features/account/components/UserSupportView.js +24 -2
  31. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  32. package/dist/features/account/hooks/useAddresses.js +4 -1
  33. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  34. package/dist/features/account/hooks/useProfile.js +1 -1
  35. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  36. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  37. package/dist/features/admin/actions/chat-actions.js +5 -1
  38. package/dist/features/admin/components/AdminAdsView.js +1 -1
  39. package/dist/features/admin/components/AdminBidsView.js +6 -1
  40. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  41. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  42. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  43. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  44. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  45. package/dist/features/admin/components/AdminSidebar.js +1 -1
  46. package/dist/features/admin/components/AdminViewCards.js +2 -2
  47. package/dist/features/admin/components/DataTable.js +2 -2
  48. package/dist/features/admin/schemas/firestore.js +9 -3
  49. package/dist/features/auctions/actions/bid-actions.js +45 -4
  50. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  51. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  52. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  53. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  54. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  55. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  56. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  57. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  58. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  59. package/dist/features/auth/hooks/useAuth.js +27 -1
  60. package/dist/features/auth/permissions/constants.d.ts +1 -1
  61. package/dist/features/auth/permissions/constants.js +3 -0
  62. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  63. package/dist/features/auth/repository/session.repository.js +23 -0
  64. package/dist/features/auth/repository/user.repository.js +13 -2
  65. package/dist/features/blog/components/BlogListView.js +2 -2
  66. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  67. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  68. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  69. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  70. package/dist/features/cart/components/index.d.ts +1 -1
  71. package/dist/features/cart/components/index.js +1 -1
  72. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  73. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  74. package/dist/features/cart/utils/line-total.d.ts +25 -0
  75. package/dist/features/cart/utils/line-total.js +26 -0
  76. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  77. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  78. package/dist/features/categories/components/CategoryTree.js +2 -2
  79. package/dist/features/contact/components/ContactForm.js +1 -1
  80. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  81. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  82. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  83. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  84. package/dist/features/events/components/EventRafflesSection.js +14 -2
  85. package/dist/features/events/components/EventStatusBadge.js +1 -1
  86. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  87. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  88. package/dist/features/faq/components/ContactCTA.js +1 -1
  89. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  90. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  91. package/dist/features/faq/repository/faqs.repository.js +64 -54
  92. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  93. package/dist/features/faq/schemas/firestore.js +2 -2
  94. package/dist/features/faq/schemas/index.d.ts +3 -3
  95. package/dist/features/faq/schemas/index.js +1 -1
  96. package/dist/features/filters/FilterFacetSection.js +2 -2
  97. package/dist/features/forms/Toggle.js +1 -1
  98. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  99. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  100. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  101. package/dist/features/homepage/components/BrandsSection.js +1 -1
  102. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  103. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  104. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  105. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  106. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  107. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  108. package/dist/features/homepage/lib/section-renderer.js +2 -1
  109. package/dist/features/layout/AppLayoutShell.js +5 -5
  110. package/dist/features/layout/BottomActions.js +5 -5
  111. package/dist/features/layout/BottomSheet.js +1 -1
  112. package/dist/features/layout/MainNavbar.js +1 -1
  113. package/dist/features/layout/NavbarLayout.js +1 -1
  114. package/dist/features/layout/TitleBarLayout.js +3 -3
  115. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  116. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  117. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  118. package/dist/features/lottery/schemas/config-write.js +139 -0
  119. package/dist/features/media/MediaPickerModal.js +3 -1
  120. package/dist/features/media/MediaVideo.js +4 -2
  121. package/dist/features/media/components/MediaSlider.js +1 -1
  122. package/dist/features/orders/components/OrdersList.js +2 -2
  123. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  124. package/dist/features/payments/repository/payout.repository.js +14 -6
  125. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  126. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  127. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  128. package/dist/features/products/columns/productTableColumns.js +3 -3
  129. package/dist/features/products/components/CompareOverlay.js +1 -1
  130. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  131. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  132. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  133. package/dist/features/products/components/ProductDetailActions.js +11 -7
  134. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  135. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  136. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  137. package/dist/features/products/components/ProductFilters.js +15 -8
  138. package/dist/features/products/components/ProductGrid.js +8 -17
  139. package/dist/features/products/components/ProductTabs.js +7 -4
  140. package/dist/features/products/components/ProductTabsShell.js +2 -7
  141. package/dist/features/products/constants/action-defs.d.ts +21 -4
  142. package/dist/features/products/constants/action-defs.js +7 -3
  143. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  144. package/dist/features/products/repository/products.repository.d.ts +1 -1
  145. package/dist/features/products/repository/products.repository.js +27 -13
  146. package/dist/features/products/schemas/firestore.d.ts +3 -2
  147. package/dist/features/products/schemas/firestore.js +1 -1
  148. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  149. package/dist/features/reviews/actions/review-actions.js +14 -3
  150. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  151. package/dist/features/reviews/components/ReviewModal.js +1 -1
  152. package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
  153. package/dist/features/reviews/repository/reviews.repository.js +53 -13
  154. package/dist/features/reviews/schemas/firestore.d.ts +6 -0
  155. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  156. package/dist/features/search/components/Search.js +3 -3
  157. package/dist/features/search/components/SearchResultsSection.js +1 -1
  158. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  159. package/dist/features/seller/components/SellerBidsView.js +6 -1
  160. package/dist/features/seller/components/SellerSidebar.js +1 -1
  161. package/dist/features/shell/SectionForm.d.ts +21 -1
  162. package/dist/features/shell/SectionForm.js +15 -3
  163. package/dist/features/shell/build-sections.js +10 -1
  164. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  165. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  166. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  167. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  168. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  169. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  170. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  171. package/dist/features/stores/actions/store-query-actions.js +25 -1
  172. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  173. package/dist/features/stores/components/StoreHeader.js +1 -1
  174. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  175. package/dist/features/support/repository/support.repository.d.ts +12 -0
  176. package/dist/features/support/repository/support.repository.js +15 -0
  177. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  178. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  179. package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
  180. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
  181. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  182. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  183. package/dist/features/wishlist/components/index.d.ts +2 -1
  184. package/dist/features/wishlist/components/index.js +1 -1
  185. package/dist/index.d.ts +10 -3
  186. package/dist/index.js +19 -4
  187. package/dist/monitoring/error-tracking.js +39 -5
  188. package/dist/monitoring/server-logger.js +70 -32
  189. package/dist/next/components/ErrorView.js +1 -1
  190. package/dist/next/routing/route-map.d.ts +39 -7
  191. package/dist/next/routing/route-map.js +34 -4
  192. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  193. package/dist/providers/db-firebase/base.repository.js +56 -9
  194. package/dist/react/ErrorBoundary.js +9 -1
  195. package/dist/react/contexts/SessionContext.js +6 -2
  196. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  197. package/dist/react/hooks/useAuthGate.js +86 -7
  198. package/dist/schemas/registry.d.ts +3 -3
  199. package/dist/security/index.d.ts +1 -1
  200. package/dist/security/index.js +1 -1
  201. package/dist/security/pii-encrypt.d.ts +13 -5
  202. package/dist/security/pii-encrypt.js +132 -6
  203. package/dist/security/pii-schemas.d.ts +20 -4
  204. package/dist/security/pii-schemas.js +23 -5
  205. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  206. package/dist/seed/_helpers/product-search-txt.js +33 -0
  207. package/dist/seed/faq-seed-data.d.ts +16 -0
  208. package/dist/seed/faq-seed-data.js +22 -1
  209. package/dist/seed/products-art-seed-data.js +49 -7
  210. package/dist/seed/products-auctions-seed-data.js +12 -4
  211. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  212. package/dist/seed/products-classifieds-seed-data.js +65 -9
  213. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  214. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  215. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  216. package/dist/seed/products-live-items-seed-data.js +33 -5
  217. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  218. package/dist/seed/products-preorders-seed-data.js +6 -2
  219. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  220. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  221. package/dist/seed/products-standard-seed-data.js +6 -2
  222. package/dist/seed/products-stickers-seed-data.js +49 -7
  223. package/dist/seed/site-settings-seed-data.js +5 -3
  224. package/dist/seo/json-ld.d.ts +44 -2
  225. package/dist/seo/json-ld.js +57 -11
  226. package/dist/styles.css +368 -90
  227. package/dist/tailwind-utilities.css +1 -1
  228. package/dist/tokens/index.d.ts +1 -1
  229. package/dist/tokens/index.js +1 -1
  230. package/dist/tokens/themes/default-dark.js +6 -0
  231. package/dist/tokens/themes/default-light.js +5 -0
  232. package/dist/tokens/tokens.css +84 -0
  233. package/dist/ui/DataTable.js +1 -1
  234. package/dist/ui/DataTable.style.css +3 -3
  235. package/dist/ui/components/BulkActionBar.style.css +1 -3
  236. package/dist/ui/components/Button.js +15 -6
  237. package/dist/ui/components/Dropdown.style.css +1 -4
  238. package/dist/ui/components/FilterChipGroup.js +1 -1
  239. package/dist/ui/components/FilterDrawer.style.css +1 -1
  240. package/dist/ui/components/Icon.d.ts +53 -0
  241. package/dist/ui/components/Icon.js +41 -0
  242. package/dist/ui/components/IconButton.d.ts +13 -0
  243. package/dist/ui/components/IconButton.js +13 -0
  244. package/dist/ui/components/IconButton.style.css +18 -10
  245. package/dist/ui/components/ListingLayout.style.css +2 -10
  246. package/dist/ui/components/ListingToolbar.js +6 -6
  247. package/dist/ui/components/Modal.js +1 -1
  248. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  249. package/dist/ui/components/Pagination.style.css +1 -6
  250. package/dist/ui/components/RichTextEditor.js +1 -1
  251. package/dist/ui/components/RowActionMenu.style.css +1 -1
  252. package/dist/ui/components/SideDrawer.style.css +1 -1
  253. package/dist/ui/components/Slider.js +1 -1
  254. package/dist/ui/components/TabBarShell.d.ts +78 -0
  255. package/dist/ui/components/TabBarShell.js +95 -0
  256. package/dist/ui/components/Tabs.js +12 -8
  257. package/dist/ui/components/Tabs.style.css +249 -41
  258. package/dist/ui/components/ViewToggle.style.css +1 -5
  259. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  260. package/dist/ui/components/tabs-overflow.js +142 -0
  261. package/dist/ui/icons/icon-registry.d.ts +38 -0
  262. package/dist/ui/icons/icon-registry.js +37 -0
  263. package/dist/ui/index.d.ts +6 -0
  264. package/dist/ui/index.js +3 -0
  265. package/dist/utils/index.d.ts +1 -1
  266. package/dist/utils/index.js +1 -1
  267. package/dist/utils/media-url.d.ts +27 -4
  268. package/dist/utils/media-url.js +37 -6
  269. package/dist/utils/search-txt.d.ts +71 -0
  270. package/dist/utils/search-txt.js +118 -0
  271. package/package.json +1 -1
@@ -120,5 +120,5 @@ export function RichTextEditor({ value, onChange, disabled = false, className =
120
120
  editor.innerHTML = safeValue;
121
121
  }
122
122
  }, [value]);
123
- return (_jsxs("div", { className: `appkit-rich-text-editor rounded-lg border border-zinc-200 bg-[var(--appkit-color-surface)] border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${className}`, "data-section": "richtexteditor-div-582", children: [_jsx("div", { className: "appkit-rich-text-editor__toolbar flex flex-wrap gap-1 border-b border-zinc-200 p-2 border-[var(--appkit-color-border)]", "data-section": "richtexteditor-div-583", children: toolbarActions.map((action) => (_jsx("button", { type: "button", title: action.title, onClick: action.run, disabled: disabled, className: "appkit-rich-text-editor__btn rounded px-2 py-1 text-xs font-medium text-zinc-700 transition hover:bg-zinc-50 disabled:cursor-not-allowed disabled:opacity-50 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)]", children: action.label }, action.title))) }), _jsx("div", { ref: editorRef, role: "textbox", "aria-label": "Rich text editor", contentEditable: !disabled, suppressContentEditableWarning: true, "data-placeholder": placeholder, onInput: emitChange, onBlur: emitChange, className: `prose prose-sm max-w-none p-3 focus:outline-none dark:prose-invert ${minHeightClassName} ${disabled ? "cursor-not-allowed opacity-70" : ""}` })] }));
123
+ return (_jsxs("div", { className: `appkit-rich-text-editor rounded-lg border border-zinc-200 bg-[var(--appkit-color-surface)] border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${className}`, "data-section": "richtexteditor-div-582", children: [_jsx("div", { className: "appkit-rich-text-editor__toolbar flex flex-wrap gap-1 border-b p-2 border-[var(--appkit-color-border)]", "data-section": "richtexteditor-div-583", children: toolbarActions.map((action) => (_jsx("button", { type: "button", title: action.title, onClick: action.run, disabled: disabled, className: "appkit-rich-text-editor__btn rounded px-2 py-1 text-xs font-medium transition hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50 text-[var(--appkit-color-text-muted)]", children: action.label }, action.title))) }), _jsx("div", { ref: editorRef, role: "textbox", "aria-label": "Rich text editor", contentEditable: !disabled, suppressContentEditableWarning: true, "data-placeholder": placeholder, onInput: emitChange, onBlur: emitChange, className: `prose prose-sm max-w-none p-3 focus:outline-none dark:prose-invert ${minHeightClassName} ${disabled ? "cursor-not-allowed opacity-70" : ""}` })] }));
124
124
  }
@@ -57,7 +57,7 @@
57
57
  }
58
58
 
59
59
  .appkit-row-action-menu__item:hover {
60
- background: var(--appkit-color-zinc-100);
60
+ background: var(--appkit-color-surface-hover);
61
61
  }
62
62
 
63
63
  :is(.dark) .appkit-row-action-menu__item {
@@ -125,7 +125,7 @@
125
125
  box-shadow: 0 0 0 1px var(--appkit-color-border);
126
126
  }
127
127
  .appkit-side-drawer__close-btn:hover {
128
- background-color: var(--appkit-color-zinc-100);
128
+ background-color: var(--appkit-color-surface-hover);
129
129
  box-shadow: 0 0 0 1px var(--appkit-color-zinc-300);
130
130
  }
131
131
  :is(.dark) .appkit-side-drawer__close-btn {
@@ -25,7 +25,7 @@ export function Slider({ value: controlledValue, defaultValue = 0, min = 0, max
25
25
  onChangeEnd?.(value);
26
26
  };
27
27
  return (_jsxs("div", { className: ["w-full", className].filter(Boolean).join(" "), "data-section": "slider-div-607", children: [(label || showValue) && (_jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", "data-section": "slider-div-608", children: [label ? _jsx(Label, { htmlFor: inputId, children: label }) : _jsx("span", {}), showValue ? (_jsx(Span, { size: "sm", className: "tabular-nums text-[var(--appkit-color-text-muted)]", children: value })) : null] })), _jsxs("div", { className: "relative", "data-section": "slider-div-609", children: [_jsx("div", { "aria-hidden": "true", className: [
28
- "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 overflow-hidden rounded-full bg-zinc-200 bg-[var(--appkit-color-border)]",
28
+ "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 overflow-hidden rounded-full bg-[var(--appkit-color-border)]",
29
29
  SIZE_CLASSES[size],
30
30
  ].join(" "), "data-section": "slider-div-610", children: _jsx("div", { className: [
31
31
  "h-full rounded-full bg-primary",
@@ -0,0 +1,78 @@
1
+ import React from "react";
2
+ import { type SelectOption } from "./Select";
3
+ export interface TabBarShellProps {
4
+ /**
5
+ * Applied to the inner `role="tablist"` element — unchanged from the
6
+ * pre-shell `TabsList` behaviour, so existing consumers keep getting it on
7
+ * the element that actually holds the tabs. Note it is no longer the
8
+ * OUTERMOST node: a margin or width passed here applies inside the shell's
9
+ * flex row.
10
+ */
11
+ className?: string;
12
+ /** Accessible name for the tablist. */
13
+ ariaLabel?: string;
14
+ /**
15
+ * Rendered before the tablist, inside the shell but OUTSIDE the
16
+ * `role="tablist"` element — for controls that are not tabs (StoreNavTabs'
17
+ * listing-type picker). A `<select>` inside a tablist is invalid ARIA,
18
+ * since it is not a `role="tab"`.
19
+ */
20
+ leading?: React.ReactNode;
21
+ /**
22
+ * The currently-selected tab's value. Keys the scroll-into-view effect, so a
23
+ * selection that starts off-screen in an overflowing strip is scrolled to.
24
+ * Omit and that repositioning simply does not happen — overflow detection
25
+ * and the arrows work regardless.
26
+ */
27
+ activeKey?: string;
28
+ children?: React.ReactNode;
29
+ }
30
+ /**
31
+ * `TabBarShell` — the rail + overflow arrows around a `role="tablist"`.
32
+ *
33
+ * The arrows are SIBLINGS of the tablist, never children, so its accessibility
34
+ * -tree children stay exactly the `role="tab"` elements — no `aria-owns`, no
35
+ * invalid-child warnings.
36
+ */
37
+ export declare function TabBarShell({ className, ariaLabel, leading, activeKey, children, }: TabBarShellProps): React.JSX.Element;
38
+ export interface TabBarButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "role" | "aria-selected"> {
39
+ selected: boolean;
40
+ }
41
+ /**
42
+ * `TabBarButton` — a bare `role="tab"` button carrying `.appkit-tabs-trigger`.
43
+ *
44
+ * Exists because appkit's `<Button variant="ghost">` is NOT transparent: it
45
+ * has a `--appkit-color-border-subtle` fill, a 44px min-height and a
46
+ * `rounded-xl` radius, so the hand-rolled bars that used it were rendering
47
+ * grey pill chips rather than text tabs. It also wraps children in
48
+ * `.appkit-button__content` (Root Cause #68), which a tab does not want.
49
+ */
50
+ export declare function TabBarButton({ selected, className, children, ...rest }: TabBarButtonProps): React.JSX.Element;
51
+ export interface TabsNavSelectProps {
52
+ ariaLabel: string;
53
+ options: SelectOption[];
54
+ value: string;
55
+ placeholder?: string;
56
+ /**
57
+ * `false` renders the `--unselected` modifier (transparent rail) — the
58
+ * control is present but is making no "you are here" claim, which is
59
+ * StoreNavTabs' state while a non-listing tab is active.
60
+ */
61
+ selected?: boolean;
62
+ onValueChange: (value: string) => void;
63
+ className?: string;
64
+ role?: string;
65
+ }
66
+ /**
67
+ * `TabsNavSelect` — the collapsed tab bar, past `TABS_DROPDOWN_THRESHOLD`.
68
+ *
69
+ * A `<select>` that reads as navigation rather than as a form field: the
70
+ * enclosing rectangle is gone and the only chrome left is the same 2px brand
71
+ * rail an active row-tab uses. See the block comment in Tabs.style.css for
72
+ * the full reasoning, and the specificity warning above those rules.
73
+ *
74
+ * `variant="default"` and NOT `"ghost"` — ghost's `border-transparent
75
+ * bg-transparent` are `!important` utilities that would pin the border to
76
+ * transparent and block the indicator rail.
77
+ */
78
+ export declare function TabsNavSelect({ ariaLabel, options, value, placeholder, selected, onValueChange, className, role, }: TabsNavSelectProps): React.JSX.Element;
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /*
4
+ * WHY: Seven tab bars existed across this codebase, four of them hand-rolled
5
+ * copies of the same `border-b-2 / -mb-px` underline with a DIFFERENT
6
+ * active colour each. This is the one shell they all render through, so
7
+ * the look is shared by construction rather than by discipline.
8
+ *
9
+ * WHAT: The chrome around a `role="tablist"` — the rail, the overflow scroll
10
+ * arrows, and a `leading` slot for controls that are NOT tabs. Plus the
11
+ * two trigger flavours a folded bar needs: `TabBarButton` (a bare
12
+ * `role="tab"` button) and `TabsNavSelect` (the >5-tab dropdown,
13
+ * stripped of Select's input chrome).
14
+ *
15
+ * EXPORTS: TabBarShell, TabBarShellProps, TabBarButton, TabBarButtonProps,
16
+ * TabsNavSelect, TabsNavSelectProps
17
+ *
18
+ * @tag domain:ui
19
+ * @tag layer:ui
20
+ * @tag pattern:none
21
+ * @tag access:client
22
+ * @tag consumers:Tabs,StoreNavTabs,EventTabBar
23
+ * @tag sideEffects:none
24
+ */
25
+ import { useRef } from "react";
26
+ import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
27
+ import { Div } from "./Div";
28
+ import { Select } from "./Select";
29
+ import { useTabsOverflow } from "./tabs-overflow";
30
+ const UI_TAB_BAR = {
31
+ shell: "appkit-tabs-shell",
32
+ arrow: "appkit-tabs-shell__arrow",
33
+ list: "appkit-tabs-list",
34
+ trigger: "appkit-tabs-trigger",
35
+ dropdown: "appkit-tabs-dropdown",
36
+ dropdownUnselected: "appkit-tabs-dropdown--unselected",
37
+ dropdownSelect: "appkit-tabs-dropdown__select",
38
+ dropdownCaret: "appkit-tabs-dropdown__caret",
39
+ };
40
+ /**
41
+ * `TabBarShell` — the rail + overflow arrows around a `role="tablist"`.
42
+ *
43
+ * The arrows are SIBLINGS of the tablist, never children, so its accessibility
44
+ * -tree children stay exactly the `role="tab"` elements — no `aria-owns`, no
45
+ * invalid-child warnings.
46
+ */
47
+ export function TabBarShell({ className = "", ariaLabel = "Tabs", leading, activeKey = "", children, }) {
48
+ const listRef = useRef(null);
49
+ const { hasArrows, atStart, atEnd, scrollByPage } = useTabsOverflow(listRef, activeKey);
50
+ /*
51
+ * The arrows are aria-hidden + tabIndex={-1} deliberately. They duplicate
52
+ * scrolling that keyboard users already get for free: every trigger is a
53
+ * natural tab stop and browsers scroll a focused element into view, so
54
+ * WCAG 2.1.1 is satisfied by that path. Making them focusable would add two
55
+ * extra tab stops to every tab bar in the app, in front of the content the
56
+ * user is actually navigating to — a net keyboard regression for a
57
+ * redundant affordance. (HorizontalScroller's arrows ARE focusable, and
58
+ * correctly so: there they page through card content with no other
59
+ * keyboard path.)
60
+ */
61
+ return (_jsxs(Div, { className: UI_TAB_BAR.shell, "data-arrows": hasArrows ? "true" : "false", children: [leading, hasArrows && (_jsx("button", { type: "button", "aria-hidden": "true", tabIndex: -1, disabled: atStart, onClick: () => scrollByPage(-1), className: UI_TAB_BAR.arrow, children: _jsx(ChevronLeft, { className: "w-4 h-4" }) })), _jsx(Div, { ref: listRef, role: "tablist", "aria-label": ariaLabel, className: [UI_TAB_BAR.list, className].filter(Boolean).join(" "), children: children }), hasArrows && (_jsx("button", { type: "button", "aria-hidden": "true", tabIndex: -1, disabled: atEnd, onClick: () => scrollByPage(1), className: UI_TAB_BAR.arrow, children: _jsx(ChevronRight, { className: "w-4 h-4" }) }))] }));
62
+ }
63
+ /**
64
+ * `TabBarButton` — a bare `role="tab"` button carrying `.appkit-tabs-trigger`.
65
+ *
66
+ * Exists because appkit's `<Button variant="ghost">` is NOT transparent: it
67
+ * has a `--appkit-color-border-subtle` fill, a 44px min-height and a
68
+ * `rounded-xl` radius, so the hand-rolled bars that used it were rendering
69
+ * grey pill chips rather than text tabs. It also wraps children in
70
+ * `.appkit-button__content` (Root Cause #68), which a tab does not want.
71
+ */
72
+ export function TabBarButton({ selected, className = "", children, ...rest }) {
73
+ return (_jsx("button", { ...rest, type: rest.type ?? "button", role: "tab", "aria-selected": selected, className: [UI_TAB_BAR.trigger, className].filter(Boolean).join(" "), children: children }));
74
+ }
75
+ /**
76
+ * `TabsNavSelect` — the collapsed tab bar, past `TABS_DROPDOWN_THRESHOLD`.
77
+ *
78
+ * A `<select>` that reads as navigation rather than as a form field: the
79
+ * enclosing rectangle is gone and the only chrome left is the same 2px brand
80
+ * rail an active row-tab uses. See the block comment in Tabs.style.css for
81
+ * the full reasoning, and the specificity warning above those rules.
82
+ *
83
+ * `variant="default"` and NOT `"ghost"` — ghost's `border-transparent
84
+ * bg-transparent` are `!important` utilities that would pin the border to
85
+ * transparent and block the indicator rail.
86
+ */
87
+ export function TabsNavSelect({ ariaLabel, options, value, placeholder, selected = true, onValueChange, className = "", role, }) {
88
+ return (_jsxs(Div, { className: [
89
+ UI_TAB_BAR.dropdown,
90
+ selected ? "" : UI_TAB_BAR.dropdownUnselected,
91
+ className,
92
+ ]
93
+ .filter(Boolean)
94
+ .join(" "), children: [_jsx(Select, { bare: true, variant: "default", "aria-label": ariaLabel, role: role, options: options, value: value, placeholder: placeholder, onValueChange: onValueChange, className: UI_TAB_BAR.dropdownSelect }), _jsx(Div, { className: UI_TAB_BAR.dropdownCaret, "aria-hidden": "true", children: _jsx(ChevronDown, { className: "w-4 h-4" }) })] }));
95
+ }
@@ -1,17 +1,21 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { createContext, useContext, useMemo, useState } from "react";
4
- import { Select } from "./Select";
4
+ import { TabBarShell, TabsNavSelect } from "./TabBarShell";
5
5
  const UI_TABS = {
6
- list: "appkit-tabs-list",
7
- listDropdown: "appkit-tabs-list--dropdown",
8
6
  trigger: "appkit-tabs-trigger",
9
7
  content: "appkit-tabs-content",
10
8
  badge: "appkit-tabs-trigger__badge",
11
9
  };
12
- /** Past this many items, TabsList renders a <Select> dropdown instead of a
13
- * horizontal row — a scrollable row of 6+ tabs is unreadable/unreachable on
14
- * mobile widths. */
10
+ /** Past this many items, TabsList renders a dropdown instead of a horizontal
11
+ * row — a scrollable row of 6+ tabs is unreadable/unreachable on mobile
12
+ * widths.
13
+ *
14
+ * The dropdown is styled as NAVIGATION, not as a form field: see
15
+ * `TabsNavSelect` and the `.appkit-tabs-dropdown` rules in Tabs.style.css. It
16
+ * used to render a raw `<Select bare>`, which carried the full
17
+ * `.appkit-select__trigger` input chrome (bordered, rounded, surface-filled,
18
+ * 40px tall) — which is exactly why a tab bar read as an input. */
15
19
  const TABS_DROPDOWN_THRESHOLD = 5;
16
20
  const TabsContext = createContext({
17
21
  active: "",
@@ -44,9 +48,9 @@ export function TabsList({ className = "", children }) {
44
48
  disabled: item.props.disabled,
45
49
  };
46
50
  });
47
- return (_jsx(Select, { bare: true, "aria-label": "Tabs", role: "tablist", options: options, value: active, onValueChange: setActive, className: [UI_TABS.list, UI_TABS.listDropdown, className].filter(Boolean).join(" ") }));
51
+ return (_jsx(TabsNavSelect, { ariaLabel: "Tabs", role: "tablist", options: options, value: active, onValueChange: setActive, className: className }));
48
52
  }
49
- return (_jsx("div", { role: "tablist", className: [UI_TABS.list, className].filter(Boolean).join(" "), "data-section": "tabs-div-615", children: children }));
53
+ return (_jsx(TabBarShell, { className: className, ariaLabel: "Tabs", activeKey: active, children: children }));
50
54
  }
51
55
  export function TabsTrigger({ value, disabled, className = "", children, badge, }) {
52
56
  const { active, setActive } = useContext(TabsContext);
@@ -1,84 +1,292 @@
1
- /* Tabs — tabbed navigation with list, triggers, and content panels */
1
+ /* Tabs — tabbed navigation: shell (scroll arrows) + list + triggers + panels
2
+ *
3
+ * WHY THIS FILE LOOKS THE WAY IT DOES (2026-08-26):
4
+ * The active trigger used to be `var(--color-primary, #65a30d)` — a variable
5
+ * that does not exist in this token system — so EVERY active tab in the app
6
+ * rendered hardcoded lime, ignoring the theme entirely. And past
7
+ * TABS_DROPDOWN_THRESHOLD triggers the bar collapses to a <Select bare>,
8
+ * which carried the full `.appkit-select__trigger` input chrome: a bordered,
9
+ * rounded, surface-filled 40px box that read as a form field rather than as
10
+ * navigation. Both are fixed below.
11
+ */
2
12
 
3
- .appkit-tabs-list {
13
+ /* ── Shell ──────────────────────────────────────────────────────────────
14
+ Owns the rail and the arrow gutters.
15
+
16
+ The rail lives HERE and not on .appkit-tabs-list because the list is the
17
+ scroll container: `overflow-y: hidden` clips anything a trigger pushes
18
+ past its own padding box, so the old `margin-bottom: -1px` overlap never
19
+ actually rendered. On the shell the rail also runs unbroken beneath the
20
+ arrow gutters. */
21
+ .appkit-tabs-shell {
22
+ --appkit-tabs-indicator: var(--appkit-color-primary);
23
+ /* Fallback first, for engines without color-mix (Safari < 16.2): plain ink
24
+ is legible everywhere, just unbranded. */
25
+ --appkit-tabs-ink-active: var(--appkit-color-text);
26
+ /* Blend the brand toward the theme's own ink. --appkit-color-text is by
27
+ definition the max-contrast ink against that theme's background, so this
28
+ self-corrects per theme: light themes darken the brand, dark themes
29
+ lighten it. Using --appkit-color-primary raw for TEXT fails WCAG AA in
30
+ the shipped `sunset` (amber on white, ~2.2:1) and `default-light`
31
+ (~3.7:1) presets. The 2px indicator below stays pure brand — the mix
32
+ applies only to text, which is the part with a contrast requirement. */
33
+ --appkit-tabs-ink-active: color-mix(in srgb, var(--appkit-color-primary) 60%, var(--appkit-color-text));
34
+ --appkit-tabs-arrow-size: 2rem;
35
+ --appkit-tabs-veil: color-mix(in srgb, var(--appkit-color-text) 5%, transparent);
36
+
37
+ position: relative;
4
38
  display: flex;
39
+ align-items: stretch;
40
+ min-width: 0; /* survives being a flex/grid item */
5
41
  border-bottom: 1px solid var(--appkit-color-border);
6
- /* Consumers with many tabs (e.g. AdminSiteSettingsView's 17 groups) would
7
- otherwise overflow past the container with no way to reach the later
8
- triggers — found via Playwright driving a real browser at 1440px wide,
9
- where tabs 16-17 were positioned off-screen and unreachable even after
10
- scrollIntoViewIfNeeded(). Horizontal scroll keeps every tab reachable
11
- regardless of trigger count or viewport width. */
42
+ }
43
+
44
+ /* ── List — the role="tablist" element, and the scroll container ──────── */
45
+ .appkit-tabs-list {
46
+ display: flex;
47
+ align-items: stretch;
48
+ gap: var(--appkit-space-1);
49
+ flex: 1 1 auto;
50
+ min-width: 0;
12
51
  overflow-x: auto;
13
52
  overflow-y: hidden;
53
+ scroll-behavior: smooth;
54
+ /* The thin scrollbar is the no-JS / pre-hydration affordance. Once the
55
+ client shell mounts and stamps data-arrows="true", the arrows take over
56
+ and it is hidden. */
14
57
  scrollbar-width: thin;
15
58
  }
16
-
17
- /* Past TABS_DROPDOWN_THRESHOLD triggers, TabsList renders a <Select bare>
18
- instead of the flex row above — override the row-specific layout rules
19
- that don't apply to a native <select>. */
20
- .appkit-tabs-list--dropdown {
21
- display: block;
22
- overflow: visible;
23
- border-bottom: none;
24
- max-width: 20rem;
59
+ .appkit-tabs-shell[data-arrows="true"] .appkit-tabs-list {
60
+ scrollbar-width: none;
61
+ -ms-overflow-style: none;
62
+ }
63
+ .appkit-tabs-shell[data-arrows="true"] .appkit-tabs-list::-webkit-scrollbar {
64
+ display: none;
65
+ }
66
+ @media (prefers-reduced-motion: reduce) {
67
+ .appkit-tabs-list {
68
+ scroll-behavior: auto;
69
+ }
25
70
  }
26
71
 
72
+ /* ── Trigger ─────────────────────────────────────────────────────────────
73
+ Applies to <button> (TabsTrigger, TabBarButton) AND <a> (StoreNavTabs,
74
+ EventTabBar), so `border: 0; background: transparent; font: inherit`
75
+ normalise the UA button styles rather than assuming one element. */
27
76
  .appkit-tabs-trigger {
28
- margin-bottom: -1px;
29
- border-bottom: 2px solid transparent;
30
- padding: 0.5rem 1rem;
31
- font-size: 0.875rem;
77
+ position: relative;
78
+ flex: none;
79
+ display: inline-flex;
80
+ align-items: center;
81
+ gap: var(--appkit-space-2);
82
+ padding: var(--appkit-space-2-5) var(--appkit-space-3);
83
+ border: 0;
84
+ border-radius: var(--appkit-radius-sm) var(--appkit-radius-sm) 0 0;
85
+ background-color: transparent;
86
+ font: inherit;
87
+ font-size: var(--appkit-text-base);
88
+ /* Weight stays 500 in EVERY state on purpose: a 500 -> 600 swap on select
89
+ shifts every following tab a few px and re-triggers the overflow
90
+ measurement in tabs-overflow.ts. */
32
91
  font-weight: 500;
33
- transition: color 0.15s;
92
+ line-height: 1.25rem;
93
+ color: var(--appkit-color-text-muted);
94
+ text-decoration: none;
34
95
  white-space: nowrap;
35
- flex-shrink: 0;
96
+ cursor: pointer;
97
+ transition:
98
+ color var(--appkit-duration-fast) ease,
99
+ box-shadow var(--appkit-duration-fast) ease,
100
+ background-color var(--appkit-duration-fast) ease;
36
101
  }
102
+
103
+ /* The indicator is an INSET box-shadow, not a border:
104
+ - it paints inside the padding box, so the list's `overflow-y: hidden`
105
+ can never clip it (a bottom border + negative margin can, and did);
106
+ - it costs zero layout, so switching tabs cannot reflow the strip;
107
+ - it renders identically on <a> and <button>. */
37
108
  .appkit-tabs-trigger[aria-selected="true"] {
38
- border-color: var(--color-primary, #65a30d);
39
- color: var(--color-primary, #65a30d);
40
- }
41
- .appkit-tabs-trigger:not([aria-selected="true"]) {
42
- color: var(--appkit-color-zinc-600);
109
+ color: var(--appkit-tabs-ink-active);
110
+ box-shadow: inset 0 -2px 0 0 var(--appkit-tabs-indicator);
43
111
  }
112
+
44
113
  .appkit-tabs-trigger:not([aria-selected="true"]):hover {
45
114
  color: var(--appkit-color-text);
115
+ background-color: var(--appkit-tabs-veil);
116
+ box-shadow: inset 0 -2px 0 0 var(--appkit-color-border);
46
117
  }
47
- .appkit-tabs-trigger:disabled {
118
+
119
+ /* outline-offset MUST be negative — a positive or zero offset draws outside
120
+ the padding box and is clipped by the scroll container. */
121
+ .appkit-tabs-trigger:focus-visible {
122
+ outline: 2px solid var(--appkit-color-focus-ring);
123
+ outline-offset: -2px;
124
+ border-radius: var(--appkit-radius-sm);
125
+ }
126
+
127
+ .appkit-tabs-trigger:disabled,
128
+ .appkit-tabs-trigger[aria-disabled="true"] {
48
129
  cursor: not-allowed;
49
130
  opacity: 0.5;
131
+ box-shadow: none;
132
+ background-color: transparent;
50
133
  }
51
134
 
135
+ /* ── Badge ───────────────────────────────────────────────────────────── */
52
136
  .appkit-tabs-trigger__badge {
53
137
  display: inline-flex;
54
138
  align-items: center;
55
139
  justify-content: center;
56
- margin-left: 0.5rem;
57
140
  padding: 0 0.4rem;
58
141
  min-width: 1.25rem;
59
142
  height: 1.25rem;
60
143
  border-radius: 9999px;
61
144
  font-size: 0.6875rem;
62
145
  font-weight: 600;
63
- background: var(--appkit-color-surface-elevated);
146
+ /* NOT --appkit-color-surface-elevated: that token is #ffffff in light mode,
147
+ so the inactive badge was invisible on every white card. */
148
+ background-color: color-mix(in srgb, var(--appkit-color-text) 8%, transparent);
64
149
  color: var(--appkit-color-text-muted);
65
150
  }
66
151
  .appkit-tabs-trigger[aria-selected="true"] .appkit-tabs-trigger__badge {
67
- background: color-mix(in srgb, var(--appkit-color-primary) 15%, transparent);
68
- color: var(--appkit-color-primary);
152
+ background-color: color-mix(in srgb, var(--appkit-color-primary) 15%, transparent);
153
+ color: var(--appkit-tabs-ink-active);
69
154
  }
70
155
 
71
- .appkit-tabs-content {
72
- padding-top: 1rem;
73
- padding-bottom: 1rem;
156
+ /* ── Scroll arrows ───────────────────────────────────────────────────────
157
+ In-flow flex siblings of the tablist, NOT absolutely-positioned overlays.
158
+ An overlay would need a background matching whatever surface the bar
159
+ happens to sit on (bg / surface / surface-elevated all occur in this app)
160
+ and would cover the leftmost/rightmost tab. In-flow arrows need no
161
+ background at all. */
162
+ .appkit-tabs-shell__arrow {
163
+ flex: none;
164
+ align-self: stretch;
165
+ display: inline-flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ width: var(--appkit-tabs-arrow-size);
169
+ padding: 0;
170
+ border: 0;
171
+ background-color: transparent;
172
+ color: var(--appkit-color-text-muted);
173
+ cursor: pointer;
174
+ transition:
175
+ color var(--appkit-duration-fast) ease,
176
+ background-color var(--appkit-duration-fast) ease,
177
+ opacity var(--appkit-duration-fast) ease;
178
+ }
179
+ .appkit-tabs-shell__arrow:hover:not([disabled]) {
180
+ color: var(--appkit-color-text);
181
+ background-color: var(--appkit-tabs-veil);
182
+ }
183
+ .appkit-tabs-shell__arrow[disabled] {
184
+ opacity: 0.3;
185
+ cursor: default;
74
186
  }
187
+ /* RTL: the chevron must point the way the content actually moves. */
188
+ [dir="rtl"] .appkit-tabs-shell__arrow > svg {
189
+ transform: scaleX(-1);
190
+ }
191
+
192
+ /* ── Dropdown (past TABS_DROPDOWN_THRESHOLD triggers) ─────────────────────
193
+ The wrapper carries the sizing and the indicator rail; the <select> itself
194
+ is stripped back to a bare label.
75
195
 
76
- .dark .appkit-tabs-list {
77
- border-color: var(--appkit-color-border);
196
+ What makes this read as NAVIGATION rather than as an input:
197
+ 1. the enclosing rectangle is gone (border, radius, surface fill) — a
198
+ form field is legible as a field almost entirely because of its box;
199
+ 2. the only chrome left is the same 2px brand rail an active row-tab
200
+ uses, so it speaks the same language as the bars beside it;
201
+ 3. height collapses from --appkit-size-input-md (40px) to a row tab's;
202
+ 4. weight 600 + active-tab ink — input VALUES are 400-weight plain text;
203
+ 5. padding-left: 0 aligns the label flush with the content column below,
204
+ the way a section heading does (inputs are inset ~12px);
205
+ 6. a caret finally exists — Select's chevron SVG only renders in its
206
+ non-`bare` branch, while `appearance-none` applies in both, so this
207
+ control previously had no disclosure affordance whatsoever. */
208
+ .appkit-tabs-dropdown {
209
+ position: relative;
210
+ display: block;
211
+ flex: none; /* when it sits in TabBarShell's `leading` slot */
212
+ /* Select hardcodes a `w-full` utility, which is !important and unbeatable,
213
+ so the SELECT can never shrink to its content — the WRAPPER has to. With
214
+ `fit-content` the child's percentage width is treated as auto for
215
+ intrinsic sizing, so the box hugs the widest option (native <select>
216
+ behaviour) and the rail hugs the label rather than running a fixed 20rem
217
+ under a short one, which would read as an underlined input again. */
218
+ width: fit-content;
219
+ max-width: 20rem;
220
+ min-width: 0;
221
+ border-bottom: 2px solid var(--appkit-tabs-indicator);
222
+ }
223
+ /* StoreNavTabs' listing-type picker uses this while no listing tab is the
224
+ active one — same control, but making no "you are here" claim. */
225
+ .appkit-tabs-dropdown--unselected {
226
+ border-bottom-color: transparent;
78
227
  }
79
- .dark .appkit-tabs-trigger:not([aria-selected="true"]) {
80
- color: var(--appkit-color-slate-400);
228
+
229
+ /* 🛑 The compound selectors below are sized to out-specify Select.style.css.
230
+ Do NOT "simplify" them to single-class selectors: the input chrome comes
231
+ straight back and the tie falls to @import order in index.style.css.
232
+ .appkit-select__trigger (0,1,0)
233
+ .appkit-select__trigger--default:focus-visible (0,2,0)
234
+ .appkit-select__trigger--enabled:hover:not(:focus-visible) (0,3,0)
235
+ .dark .appkit-select__trigger (0,2,0) */
236
+ .appkit-tabs-dropdown__select.appkit-select__trigger {
237
+ min-height: 0; /* kills --appkit-size-input-md (40px) */
238
+ border: 0; /* kills the 1px border */
239
+ border-radius: 0; /* kills the .5rem radius */
240
+ background-color: transparent; /* kills the surface fill */
241
+ box-shadow: none;
242
+ /* padding-right is deliberately NOT set: Select hardcodes the `pr-10`
243
+ utility, which is !important and unbeatable. 2.5rem is exactly the caret
244
+ gutter we want anyway. */
245
+ padding-top: var(--appkit-space-2-5);
246
+ padding-bottom: var(--appkit-space-2-5);
247
+ padding-left: 0;
248
+ font-size: var(--appkit-text-base);
249
+ font-weight: 600;
250
+ color: var(--appkit-tabs-ink-active);
251
+ cursor: pointer;
81
252
  }
82
- .dark .appkit-tabs-trigger:not([aria-selected="true"]):hover {
253
+ .appkit-tabs-dropdown__select.appkit-select__trigger:hover:not(:focus-visible) {
254
+ border-color: transparent;
83
255
  color: var(--appkit-color-text);
84
256
  }
257
+ .appkit-tabs-dropdown__select.appkit-select__trigger:focus-visible {
258
+ outline: 2px solid var(--appkit-color-focus-ring);
259
+ outline-offset: 2px;
260
+ border-color: transparent;
261
+ box-shadow: none;
262
+ }
263
+
264
+ .appkit-tabs-dropdown__caret {
265
+ position: absolute;
266
+ top: 50%;
267
+ right: var(--appkit-space-2);
268
+ transform: translateY(-50%);
269
+ display: inline-flex;
270
+ pointer-events: none;
271
+ color: var(--appkit-tabs-ink-active);
272
+ }
273
+
274
+ /* ── Panels ──────────────────────────────────────────────────────────── */
275
+ .appkit-tabs-content {
276
+ padding-top: var(--appkit-space-4);
277
+ padding-bottom: var(--appkit-space-4);
278
+ }
279
+
280
+ /* ── Dark mode ───────────────────────────────────────────────────────────
281
+ Exactly ONE override is needed. Every colour above is either a semantic
282
+ token that flips with the theme, or a color-mix against
283
+ --appkit-color-text / --appkit-color-primary, which flip with it too. This
284
+ rule exists solely because Select.style.css hardcodes a dark background at
285
+ (0,2,0). The old `.dark .appkit-tabs-trigger { color: slate-400 }` pair is
286
+ gone — it pinned a fixed ramp value that ignored the cobalt-night and
287
+ sunset themes. */
288
+ .dark .appkit-tabs-dropdown__select.appkit-select__trigger {
289
+ background-color: transparent;
290
+ border-color: transparent;
291
+ color: var(--appkit-tabs-ink-active);
292
+ }
@@ -26,7 +26,7 @@
26
26
  }
27
27
 
28
28
  .appkit-view-toggle__button--inactive:hover {
29
- background: var(--appkit-color-zinc-100);
29
+ background: var(--appkit-color-surface-hover);
30
30
  }
31
31
 
32
32
  .appkit-view-toggle__icon {
@@ -44,7 +44,3 @@
44
44
  color: var(--appkit-color-slate-400);
45
45
  border-color: var(--appkit-color-border);
46
46
  }
47
-
48
- .dark .appkit-view-toggle__button--inactive:hover {
49
- background: var(--appkit-color-slate-800);
50
- }
@@ -0,0 +1,17 @@
1
+ export interface TabsOverflowState {
2
+ /** True only once the client has mounted AND the strip actually overflows. */
3
+ hasArrows: boolean;
4
+ atStart: boolean;
5
+ atEnd: boolean;
6
+ }
7
+ export interface TabsOverflowApi extends TabsOverflowState {
8
+ scrollByPage: (direction: 1 | -1) => void;
9
+ }
10
+ /**
11
+ * Drives the scroll-arrow affordances on a horizontally scrollable tab list.
12
+ *
13
+ * `activeKey` identifies the currently-selected tab. It exists solely to key
14
+ * the scroll-into-view effect, so the strip repositions when the selection
15
+ * changes and stays put while the user scrolls.
16
+ */
17
+ export declare function useTabsOverflow(listRef: React.RefObject<HTMLElement | null>, activeKey: string): TabsOverflowApi;