@mohasinac/appkit 4.30.16 → 4.30.17
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/_internal/shared/types/action-error-reporter.d.ts +40 -0
- package/dist/_internal/shared/types/action-error-reporter.js +27 -0
- package/dist/_internal/shared/types/action-result.d.ts +10 -0
- package/dist/_internal/shared/types/action-result.js +26 -0
- package/dist/client/api/surface-error.d.ts +6 -4
- package/dist/client/api/surface-error.js +19 -8
- package/dist/contracts/realtime-session.d.ts +27 -0
- package/dist/contracts/realtime-session.js +118 -0
- package/dist/core/unit-of-work.js +21 -13
- package/dist/errors/base-error.js +14 -1
- package/dist/errors/database-error.js +11 -1
- package/dist/errors/describe-cause.d.ts +5 -0
- package/dist/errors/describe-cause.js +76 -0
- package/dist/errors/error-display-map.d.ts +32 -1
- package/dist/errors/error-display-map.js +46 -0
- package/dist/errors/error-mapping.d.ts +47 -7
- package/dist/errors/error-mapping.js +46 -5
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +4 -0
- package/dist/features/about/components/PublicProfileView.js +4 -4
- package/dist/features/account/components/AddressBook.js +2 -2
- package/dist/features/account/components/AddressesIndexListing.js +2 -2
- package/dist/features/admin/components/AdminAnalyticsView.js +2 -2
- package/dist/features/admin/components/AdminCouponsView.js +2 -2
- package/dist/features/admin/components/AdminGuideHubView.js +2 -2
- package/dist/features/admin/components/AdminMediaView.js +2 -2
- package/dist/features/admin/components/AdminViewCards.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +1 -1
- package/dist/features/auctions/components/MarketplaceAuctionGrid.d.ts +1 -2
- package/dist/features/auctions/components/MarketplaceAuctionGrid.js +8 -7
- package/dist/features/auctions/components/PlaceBidFormClient.js +32 -15
- package/dist/features/before-after/components/BeforeAfterGallery.js +2 -2
- package/dist/features/blog/components/BlogIndexListing.js +3 -3
- package/dist/features/blog/components/BlogListView.js +3 -3
- package/dist/features/blog/components/BlogPostView.js +2 -2
- package/dist/features/categories/components/BundleCollage.js +2 -2
- package/dist/features/categories/components/CategoriesIndexListing.js +3 -3
- package/dist/features/categories/components/CategoryBundlesListing.js +2 -2
- package/dist/features/categories/components/CategoryGrid.js +2 -2
- package/dist/features/categories/components/CategoryProductsListing.js +2 -2
- package/dist/features/categories/components/CategoryStoresListing.js +2 -2
- package/dist/features/collections/components/CollectionCard.js +2 -2
- package/dist/features/events/components/EventRafflesSection.js +2 -2
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/components/EventsListView.js +3 -3
- package/dist/features/homepage/components/FeaturedBundlesSection.js +2 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +1 -1
- package/dist/features/homepage/components/SecurityHighlightsSection.js +1 -1
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/StatsCounterSection.js +1 -1
- package/dist/features/homepage/components/TrustBadges.js +2 -2
- package/dist/features/homepage/components/TrustFeaturesSection.js +1 -1
- package/dist/features/messages/hooks/useConversation.js +39 -15
- package/dist/features/messages/hooks/useConversations.js +36 -14
- package/dist/features/messages/repository/conversations.repository.d.ts +23 -0
- package/dist/features/messages/repository/conversations.repository.js +53 -0
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -3
- package/dist/features/products/components/AuctionsIndexListing.js +2 -3
- package/dist/features/products/components/PrizeDrawCollage.js +2 -2
- package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -3
- package/dist/features/products/components/PrizeDrawsSection.js +2 -2
- package/dist/features/products/components/ProductGrid.js +2 -7
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/RelatedProducts.js +2 -2
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -2
- package/dist/features/reviews/components/ReviewsListingPanel.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +2 -2
- package/dist/features/seller/components/SellerDashboardView.js +3 -3
- package/dist/features/server-errors/schemas/firestore.d.ts +9 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -3
- package/dist/features/stores/components/StoreClassifiedsListing.js +2 -3
- package/dist/features/stores/components/StoreDigitalCodesListing.js +2 -3
- package/dist/features/stores/components/StoreGuideHubView.js +2 -2
- package/dist/features/stores/components/StoreLiveItemsListing.js +2 -3
- package/dist/features/stores/components/StorePreOrdersListing.js +2 -3
- package/dist/features/stores/components/StoreProductsListing.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +3 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +54 -1
- package/dist/next/api/routeHandler.js +27 -7
- package/dist/providers/db-firebase/base.repository.js +34 -18
- package/dist/react/hooks/useRealtimeEvent.js +22 -4
- package/dist/server.d.ts +2 -0
- package/dist/server.js +5 -0
- package/dist/styles.css +84 -20
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/tokens.css +7 -2
- package/dist/ui/components/Layout.d.ts +31 -18
- package/dist/ui/components/Layout.js +30 -19
- package/dist/ui/components/Layout.style.css +76 -17
- package/dist/ui/components/ListingLayout.d.ts +2 -0
- package/dist/ui/components/ListingLayout.js +16 -2
- package/dist/ui/components/StickyToolbar.js +3 -48
- package/dist/ui/components/toolbar-collapse.d.ts +44 -0
- package/dist/ui/components/toolbar-collapse.js +93 -0
- package/package.json +1 -1
package/dist/tokens/tokens.css
CHANGED
|
@@ -757,10 +757,15 @@
|
|
|
757
757
|
|
|
758
758
|
/* --- Grid ------------------------------------------------------------------ */
|
|
759
759
|
:root {
|
|
760
|
-
/* Minimum column widths for auto-fill grid layouts (used in minmax())
|
|
760
|
+
/* Minimum column widths for auto-fill grid layouts (used in minmax()).
|
|
761
|
+
`--appkit-grid-min-card` is the canonical card-grid minimum consumed by
|
|
762
|
+
.appkit-grid--cards. At 260px the lg tier (960px of content inside the
|
|
763
|
+
max-w-screen-xl container) resolves to 3 columns and the xl tier
|
|
764
|
+
(1216px, that container's cap) to 4 — i.e. "3 or 4 depending on the
|
|
765
|
+
available width". Changing it re-columns every listing in the app. */
|
|
761
766
|
--appkit-grid-min-card-xs: 130px;
|
|
762
767
|
--appkit-grid-min-card-sm: 160px;
|
|
763
|
-
--appkit-grid-min-card:
|
|
768
|
+
--appkit-grid-min-card: 260px;
|
|
764
769
|
--appkit-grid-min-card-lg: 300px;
|
|
765
770
|
}
|
|
766
771
|
|
|
@@ -106,26 +106,30 @@ export declare const GRID_MAP: {
|
|
|
106
106
|
/** 2 → 3 → 4 → 5 → 6 */
|
|
107
107
|
readonly 6: "appkit-grid appkit-grid--6";
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
109
|
+
* THE canonical card grid — use this for every listing surface.
|
|
110
|
+
* 1 column on phones → 2 from md → auto-fill (3 at lg, 4 at xl) above,
|
|
111
|
+
* so the column count follows the grid's own width rather than the
|
|
112
|
+
* viewport's. See the "Canonical card grid" block in Layout.style.css.
|
|
111
113
|
*/
|
|
112
114
|
readonly cards: "appkit-grid appkit-grid--cards";
|
|
113
|
-
/**
|
|
114
|
-
readonly
|
|
115
|
-
/**
|
|
116
|
-
readonly
|
|
117
|
-
/**
|
|
118
|
-
readonly
|
|
119
|
-
/**
|
|
120
|
-
readonly
|
|
121
|
-
/**
|
|
122
|
-
readonly
|
|
123
|
-
/**
|
|
124
|
-
readonly
|
|
125
|
-
/**
|
|
126
|
-
readonly
|
|
127
|
-
/**
|
|
128
|
-
readonly
|
|
115
|
+
/** Card ladder at a 300 px floor — wide cards (addresses, coupons). */
|
|
116
|
+
readonly cardsWide: "appkit-grid appkit-grid--cards appkit-grid--cards-wide";
|
|
117
|
+
/** @deprecated alias of `cards` — kept so existing callers keep compiling. */
|
|
118
|
+
readonly productCards: "appkit-grid appkit-grid--cards";
|
|
119
|
+
/** @deprecated alias of `cards` */
|
|
120
|
+
readonly productCardsCompact: "appkit-grid appkit-grid--cards";
|
|
121
|
+
/** @deprecated alias of `cards` */
|
|
122
|
+
readonly storeCards: "appkit-grid appkit-grid--cards";
|
|
123
|
+
/** Card ladder at a 130 px floor — category tiles. */
|
|
124
|
+
readonly categoryCards: "appkit-grid appkit-grid--cards appkit-grid--cards-tile";
|
|
125
|
+
/** @deprecated alias of `cardsWide` */
|
|
126
|
+
readonly couponCards: "appkit-grid appkit-grid--cards appkit-grid--cards-wide";
|
|
127
|
+
/** @deprecated alias of `cardsWide` */
|
|
128
|
+
readonly addressCards: "appkit-grid appkit-grid--cards appkit-grid--cards-wide";
|
|
129
|
+
/** Card ladder at a 180 px floor — KPI/stat tiles. */
|
|
130
|
+
readonly statTiles: "appkit-grid appkit-grid--cards appkit-grid--cards-stat";
|
|
131
|
+
/** Card ladder at a 160 px floor — account nav tiles. */
|
|
132
|
+
readonly navTiles: "appkit-grid appkit-grid--cards appkit-grid--cards-nav";
|
|
129
133
|
/** Equal halves on md+ */
|
|
130
134
|
readonly halves: "appkit-grid appkit-grid--halves";
|
|
131
135
|
/** 2fr / 1fr split on md+ */
|
|
@@ -346,6 +350,10 @@ export declare function Row({ gap, centered, align, justify, textSize, textWeigh
|
|
|
346
350
|
export interface GridProps extends React.HTMLAttributes<HTMLElement>, SurfaceProps {
|
|
347
351
|
/**
|
|
348
352
|
* Column preset.
|
|
353
|
+
* - **`"cards"` → the canonical card grid. Use it for every listing surface**
|
|
354
|
+
* (1 col on phones → 2 from md → auto-fill 3–4 above). `"cardsWide"`,
|
|
355
|
+
* `"categoryCards"`, `"statTiles"` and `"navTiles"` are the same ladder at
|
|
356
|
+
* a different minmax() floor.
|
|
349
357
|
* - Numbers `1`–`6` → mobile-first responsive stacks
|
|
350
358
|
* - `"halves"` → equal 2-col on md+
|
|
351
359
|
* - `"sidebar"` / `"sidebarRight"` / `"sidebarWide"` → fixed+flexible splits
|
|
@@ -354,6 +362,11 @@ export interface GridProps extends React.HTMLAttributes<HTMLElement>, SurfacePro
|
|
|
354
362
|
* - Omit (or `undefined`) to use a raw `grid` base and supply columns via
|
|
355
363
|
* the `className` prop directly (e.g. `className="grid-cols-2"`). Useful
|
|
356
364
|
* for fixed non-responsive column counts (form field pairs, button rows).
|
|
365
|
+
*
|
|
366
|
+
* 🛑 A `grid-cols-*` className BEATS `cols`. appkit's dist/styles.css is
|
|
367
|
+
* unlayered and both tailwind configs set `important: true`, so a Tailwind
|
|
368
|
+
* utility in `@layer utilities` outranks the preset's own rule. Passing both
|
|
369
|
+
* silently ignores `cols` — pass one or the other, never both.
|
|
357
370
|
*/
|
|
358
371
|
cols?: GridCols;
|
|
359
372
|
/** Space between grid cells. Defaults to `"md"` (`gap-4`). */
|
|
@@ -77,6 +77,13 @@ const CONTAINER_MAP = {
|
|
|
77
77
|
/** `max-w-[1920px]` — ultra-wide / 4K displays */
|
|
78
78
|
ultra: "appkit-container--ultra",
|
|
79
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* The canonical card ladder, and its wide-floor variant. Named because several
|
|
82
|
+
* `GRID_MAP` keys are deliberate aliases of them — see the `@deprecated`
|
|
83
|
+
* entries below.
|
|
84
|
+
*/
|
|
85
|
+
const CARDS_LADDER = "appkit-grid appkit-grid--cards";
|
|
86
|
+
const CARDS_WIDE_LADDER = `${CARDS_LADDER} appkit-grid--cards-wide`;
|
|
80
87
|
/**
|
|
81
88
|
* Responsive grid column presets.
|
|
82
89
|
* Mirrors THEME_CONSTANTS.grid in the host app.
|
|
@@ -95,26 +102,30 @@ export const GRID_MAP = {
|
|
|
95
102
|
/** 2 → 3 → 4 → 5 → 6 */
|
|
96
103
|
6: "appkit-grid appkit-grid--6",
|
|
97
104
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
105
|
+
* THE canonical card grid — use this for every listing surface.
|
|
106
|
+
* 1 column on phones → 2 from md → auto-fill (3 at lg, 4 at xl) above,
|
|
107
|
+
* so the column count follows the grid's own width rather than the
|
|
108
|
+
* viewport's. See the "Canonical card grid" block in Layout.style.css.
|
|
100
109
|
*/
|
|
101
|
-
cards:
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
|
|
110
|
+
cards: CARDS_LADDER,
|
|
111
|
+
/** Card ladder at a 300 px floor — wide cards (addresses, coupons). */
|
|
112
|
+
cardsWide: CARDS_WIDE_LADDER,
|
|
113
|
+
/** @deprecated alias of `cards` — kept so existing callers keep compiling. */
|
|
114
|
+
productCards: CARDS_LADDER,
|
|
115
|
+
/** @deprecated alias of `cards` */
|
|
116
|
+
productCardsCompact: CARDS_LADDER,
|
|
117
|
+
/** @deprecated alias of `cards` */
|
|
118
|
+
storeCards: CARDS_LADDER,
|
|
119
|
+
/** Card ladder at a 130 px floor — category tiles. */
|
|
120
|
+
categoryCards: `${CARDS_LADDER} appkit-grid--cards-tile`,
|
|
121
|
+
/** @deprecated alias of `cardsWide` */
|
|
122
|
+
couponCards: CARDS_WIDE_LADDER,
|
|
123
|
+
/** @deprecated alias of `cardsWide` */
|
|
124
|
+
addressCards: CARDS_WIDE_LADDER,
|
|
125
|
+
/** Card ladder at a 180 px floor — KPI/stat tiles. */
|
|
126
|
+
statTiles: `${CARDS_LADDER} appkit-grid--cards-stat`,
|
|
127
|
+
/** Card ladder at a 160 px floor — account nav tiles. */
|
|
128
|
+
navTiles: `${CARDS_LADDER} appkit-grid--cards-nav`,
|
|
118
129
|
/** Equal halves on md+ */
|
|
119
130
|
halves: "appkit-grid appkit-grid--halves",
|
|
120
131
|
/** 2fr / 1fr split on md+ */
|
|
@@ -110,15 +110,16 @@
|
|
|
110
110
|
.appkit-grid--4 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
111
111
|
.appkit-grid--5 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
112
112
|
.appkit-grid--6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
113
|
-
.appkit-grid--cards
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.appkit-
|
|
121
|
-
.
|
|
113
|
+
/* .appkit-grid--cards and its density modifiers live in the "Canonical card
|
|
114
|
+
grid" block at the end of this file — they are a three-step ladder, not a
|
|
115
|
+
one-liner, and the flat rules that used to sit here (--product-cards,
|
|
116
|
+
--product-cards-compact, --store-cards, --coupon-cards, --address-cards,
|
|
117
|
+
--category-cards, --stat-tiles, --nav-tiles) are now GRID_MAP aliases of it.
|
|
118
|
+
|
|
119
|
+
Their hardcoded `gap` went with them deliberately: it sat AFTER
|
|
120
|
+
.appkit-gap--md at equal specificity in this same unlayered file, so it
|
|
121
|
+
silently beat the `gap` prop. Density belongs in the minmax() floor, not in
|
|
122
|
+
a gap the caller thinks they control. */
|
|
122
123
|
.appkit-grid--halves { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
123
124
|
.appkit-grid--two-third { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
124
125
|
.appkit-grid--one-third { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
@@ -154,8 +155,7 @@
|
|
|
154
155
|
.appkit-grid--2,
|
|
155
156
|
.appkit-grid--3,
|
|
156
157
|
.appkit-grid--4,
|
|
157
|
-
.appkit-grid--5
|
|
158
|
-
.appkit-grid--cards {
|
|
158
|
+
.appkit-grid--5 {
|
|
159
159
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -165,8 +165,7 @@
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
@media (min-width: 768px) {
|
|
168
|
-
.appkit-grid--5
|
|
169
|
-
.appkit-grid--cards {
|
|
168
|
+
.appkit-grid--5 {
|
|
170
169
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
171
170
|
}
|
|
172
171
|
|
|
@@ -190,8 +189,7 @@
|
|
|
190
189
|
@media (min-width: 1024px) {
|
|
191
190
|
.appkit-grid--3,
|
|
192
191
|
.appkit-grid--4,
|
|
193
|
-
.appkit-grid--5
|
|
194
|
-
.appkit-grid--cards {
|
|
192
|
+
.appkit-grid--5 {
|
|
195
193
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
196
194
|
}
|
|
197
195
|
|
|
@@ -238,8 +236,7 @@
|
|
|
238
236
|
}
|
|
239
237
|
|
|
240
238
|
.appkit-grid--4,
|
|
241
|
-
.appkit-grid--5
|
|
242
|
-
.appkit-grid--cards {
|
|
239
|
+
.appkit-grid--5 {
|
|
243
240
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
244
241
|
}
|
|
245
242
|
|
|
@@ -252,6 +249,68 @@
|
|
|
252
249
|
}
|
|
253
250
|
}
|
|
254
251
|
|
|
252
|
+
/* === Canonical card grid ===================================================
|
|
253
|
+
ONE ladder for every "cards laid out in a grid" surface — product, store,
|
|
254
|
+
event, blog, coupon, review, address, category tile, KPI tile, and the
|
|
255
|
+
admin/seller card views:
|
|
256
|
+
|
|
257
|
+
base 1 column — the card takes the whole content width
|
|
258
|
+
>= 768px 2 columns
|
|
259
|
+
>= 1024px repeat(auto-fill, minmax(--appkit-grid-cards-min, 1fr))
|
|
260
|
+
|
|
261
|
+
🛑 `auto-fill`, NEVER `auto-fit`. `auto-fit` collapses the empty tracks in a
|
|
262
|
+
short final row and lets `1fr` absorb the freed space, so a row holding a
|
|
263
|
+
single leftover card stretches it across the entire container. `auto-fill`
|
|
264
|
+
keeps the empty tracks at their computed size, so the lone card in the last
|
|
265
|
+
row is exactly as wide as its siblings in the row above. That is the whole
|
|
266
|
+
reason the >=1024px tier is auto-fill rather than a fixed column count — do
|
|
267
|
+
not "optimise" it to auto-fit.
|
|
268
|
+
|
|
269
|
+
auto-fill resolves against the GRID's own content box, not the viewport, so
|
|
270
|
+
this one class self-corrects inside ListingLayout's desktop filter sidebar
|
|
271
|
+
(15/16/18rem at lg/xl/2xl — .appkit-listing-layout__content is
|
|
272
|
+
`flex:1; min-width:0`) and inside the dashboard content rail. No
|
|
273
|
+
sidebar-aware breakpoints and no container queries are needed. The 1->2 step
|
|
274
|
+
stays viewport-based on purpose: a phone gets one card whatever its
|
|
275
|
+
container says.
|
|
276
|
+
|
|
277
|
+
Column counts for the default 260px floor:
|
|
278
|
+
lg (1024px vp -> 960px content) -> 3
|
|
279
|
+
xl+ (max-w-screen-xl caps content at 1216px) -> 4
|
|
280
|
+
i.e. "3 or 4 based on the available width" on every public page. Dashboards
|
|
281
|
+
are capped by max-w-screen-2xl instead and reach 5 on an ultrawide monitor —
|
|
282
|
+
a wider container genuinely earning another column, at ~275px per card.
|
|
283
|
+
|
|
284
|
+
🛑 CASCADE: appkit's dist/styles.css is UNLAYERED and both tailwind configs
|
|
285
|
+
set `important: true`, so a `.lg\:grid-cols-4` utility in @layer utilities
|
|
286
|
+
BEATS these rules. A call site using cols="cards" must NOT also carry a
|
|
287
|
+
grid-cols-* className — it would silently win and this ladder would do
|
|
288
|
+
nothing. scripts/audit-card-grid-cols.mjs enforces that. */
|
|
289
|
+
|
|
290
|
+
.appkit-grid--cards {
|
|
291
|
+
--appkit-grid-cards-min: var(--appkit-grid-min-card);
|
|
292
|
+
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Density modifiers — each overrides ONLY the local floor; the ladder above is
|
|
296
|
+
shared, so every card grid changes shape together. */
|
|
297
|
+
.appkit-grid--cards-wide { --appkit-grid-cards-min: var(--appkit-grid-min-card-lg); }
|
|
298
|
+
.appkit-grid--cards-stat { --appkit-grid-cards-min: 180px; }
|
|
299
|
+
.appkit-grid--cards-nav { --appkit-grid-cards-min: var(--appkit-grid-min-card-sm); }
|
|
300
|
+
.appkit-grid--cards-tile { --appkit-grid-cards-min: var(--appkit-grid-min-card-xs); }
|
|
301
|
+
|
|
302
|
+
@media (min-width: 768px) {
|
|
303
|
+
.appkit-grid--cards {
|
|
304
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
@media (min-width: 1024px) {
|
|
309
|
+
.appkit-grid--cards {
|
|
310
|
+
grid-template-columns: repeat(auto-fill, minmax(var(--appkit-grid-cards-min), 1fr));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
255
314
|
/* Variant catalogue: divider primitives for Stack/Row. `divide` prop emits a
|
|
256
315
|
themed border-on-children rule that respects the active theme via CSS vars. */
|
|
257
316
|
.appkit-stack--divide > * + * { border-top: 1px solid var(--appkit-color-border); padding-top: var(--appkit-space-3); }
|
|
@@ -28,6 +28,8 @@ export interface ListingLayoutLabels {
|
|
|
28
28
|
showFilters?: string;
|
|
29
29
|
hideFilters?: string;
|
|
30
30
|
filterActiveCount?: (count: number) => string;
|
|
31
|
+
/** Noun in the mobile toolbar's "Show/Hide …" collapse strip. */
|
|
32
|
+
toolbarLabel?: string;
|
|
31
33
|
}
|
|
32
34
|
export interface ListingLayoutProps {
|
|
33
35
|
headerSlot?: ReactNode;
|
|
@@ -21,6 +21,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
21
21
|
*/
|
|
22
22
|
import { useState } from "react";
|
|
23
23
|
import { createPortal } from "react-dom";
|
|
24
|
+
import { usePathname } from "next/navigation";
|
|
25
|
+
import { CollapseStrip, useToolbarCollapse } from "./toolbar-collapse";
|
|
24
26
|
import { useBottomChromeSlot } from "./bottom-chrome-slot";
|
|
25
27
|
import { Aside, Nav } from "./Semantic";
|
|
26
28
|
import { Text, Span } from "./Typography";
|
|
@@ -36,6 +38,7 @@ const DEFAULT_LABELS = {
|
|
|
36
38
|
showFilters: "Show filters",
|
|
37
39
|
hideFilters: "Hide filters",
|
|
38
40
|
filterActiveCount: (count) => `${count} active`,
|
|
41
|
+
toolbarLabel: "Toolbar",
|
|
39
42
|
};
|
|
40
43
|
export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount = 0, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount = 0, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen = false, filterPendingCount, className = "", errorSlot, labels, portal, overlays, detailView, }) {
|
|
41
44
|
// useState must run unconditionally on every render — these were
|
|
@@ -54,6 +57,17 @@ export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filte
|
|
|
54
57
|
// Portal-derived isDashboard default (folded from ListingViewShell)
|
|
55
58
|
const effectiveIsDashboard = isDashboard ?? (portal === "admin" || portal === "seller");
|
|
56
59
|
const l = { ...DEFAULT_LABELS, ...labels };
|
|
60
|
+
/* The mobile toolbar is two stacked rows — search + Filter, then a scrolling
|
|
61
|
+
sort/view/actions chip row — roughly 100px of chrome above the first card.
|
|
62
|
+
It opens collapsed below `lg` and stays as the user left it thereafter.
|
|
63
|
+
`forceExpanded` on a selection is load-bearing: the bulk-action bar renders
|
|
64
|
+
INSIDE this toolbar, so a collapsed one would leave the user no way to act
|
|
65
|
+
on rows they had just selected. */
|
|
66
|
+
const toolbarPathname = usePathname();
|
|
67
|
+
const { collapsed: mobileToolbarCollapsed, toggle: toggleMobileToolbar } = useToolbarCollapse({
|
|
68
|
+
id: `listing-layout${toolbarPathname ? `:${toolbarPathname}` : ""}`,
|
|
69
|
+
forceExpanded: selectedCount > 0,
|
|
70
|
+
});
|
|
57
71
|
const hasFilter = Boolean(filterContent);
|
|
58
72
|
const panelTitle = filterTitle ?? l.filtersTitle;
|
|
59
73
|
const handleMobileApply = () => {
|
|
@@ -78,12 +92,12 @@ export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filte
|
|
|
78
92
|
].join(" "), children: [_jsxs(Div, { className: "appkit-listing-layout__toolbar-row appkit-listing-layout__toolbar-row--desktop", children: [hasFilter && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setSidebarOpen((prev) => !prev), "aria-label": sidebarOpen ? l.hideFilters : l.showFilters, "aria-expanded": sidebarOpen, className: [
|
|
79
93
|
"appkit-listing-layout__filter-btn",
|
|
80
94
|
sidebarOpen ? "appkit-listing-layout__filter-btn--active" : "",
|
|
81
|
-
].join(" "), children: [_jsx(FilterIcon, {}), l.filtersTitle, filterActiveCount > 0 && (_jsx(Span, { className: "appkit-listing-layout__filter-badge", "aria-label": l.filterActiveCount(filterActiveCount), children: filterActiveCount }))] })), searchSlot && (_jsx(Div, { className: "appkit-listing-layout__search", children: searchSlot })), _jsxs(Div, { className: "appkit-listing-layout__toolbar-actions", children: [sortSlot, viewToggleSlot && (_jsx(Div, { className: "appkit-listing-layout__view-toggle-wrap", children: viewToggleSlot })), actionsSlot] }), toolbarPaginationSlot && (_jsx(Div, { className: "appkit-listing-layout__toolbar-pagination", children: toolbarPaginationSlot }))] }), _jsxs(Div, { className: "appkit-listing-layout__toolbar-row appkit-listing-layout__toolbar-row--mobile", children: [_jsxs(Div, { className: "appkit-listing-layout__mobile-row-1", children: [hasFilter && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setMobileFilterOpen(true), "aria-label": l.filtersTitle, className: [
|
|
95
|
+
].join(" "), children: [_jsx(FilterIcon, {}), l.filtersTitle, filterActiveCount > 0 && (_jsx(Span, { className: "appkit-listing-layout__filter-badge", "aria-label": l.filterActiveCount(filterActiveCount), children: filterActiveCount }))] })), searchSlot && (_jsx(Div, { className: "appkit-listing-layout__search", children: searchSlot })), _jsxs(Div, { className: "appkit-listing-layout__toolbar-actions", children: [sortSlot, viewToggleSlot && (_jsx(Div, { className: "appkit-listing-layout__view-toggle-wrap", children: viewToggleSlot })), actionsSlot] }), toolbarPaginationSlot && (_jsx(Div, { className: "appkit-listing-layout__toolbar-pagination", children: toolbarPaginationSlot }))] }), !mobileToolbarCollapsed && (_jsxs(Div, { className: "appkit-listing-layout__toolbar-row appkit-listing-layout__toolbar-row--mobile", children: [_jsxs(Div, { className: "appkit-listing-layout__mobile-row-1", children: [hasFilter && (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setMobileFilterOpen(true), "aria-label": l.filtersTitle, className: [
|
|
82
96
|
"appkit-listing-layout__filter-btn",
|
|
83
97
|
filterActiveCount > 0
|
|
84
98
|
? "appkit-listing-layout__filter-btn--active"
|
|
85
99
|
: "",
|
|
86
|
-
].join(" "), children: [_jsx(FilterIcon, {}), l.filtersTitle, filterActiveCount > 0 && (_jsx(Span, { className: "appkit-listing-layout__filter-badge", "aria-label": l.filterActiveCount(filterActiveCount), children: filterActiveCount }))] })), searchSlot && (_jsx(Div, { className: "appkit-listing-layout__search", children: searchSlot }))] }), (sortSlot || viewToggleSlot || actionsSlot) && (_jsx(Div, { className: "appkit-listing-layout__mobile-row-2", children: _jsxs(Div, { className: "appkit-listing-layout__mobile-row-2__inner", children: [sortSlot, viewToggleSlot, actionsSlot] }) }))] }), selectedCount > 0 && (_jsx(Div, { className: "appkit-listing-layout__bulk-bar", children: _jsx(BulkActionBar, { selectedCount: selectedCount, onClearSelection: onClearSelection, actions: bulkActionItems, labels: {
|
|
100
|
+
].join(" "), children: [_jsx(FilterIcon, {}), l.filtersTitle, filterActiveCount > 0 && (_jsx(Span, { className: "appkit-listing-layout__filter-badge", "aria-label": l.filterActiveCount(filterActiveCount), children: filterActiveCount }))] })), searchSlot && (_jsx(Div, { className: "appkit-listing-layout__search", children: searchSlot }))] }), (sortSlot || viewToggleSlot || actionsSlot) && (_jsx(Div, { className: "appkit-listing-layout__mobile-row-2", children: _jsxs(Div, { className: "appkit-listing-layout__mobile-row-2__inner", children: [sortSlot, viewToggleSlot, actionsSlot] }) }))] })), selectedCount === 0 && (_jsx(CollapseStrip, { collapsed: mobileToolbarCollapsed, label: l.toolbarLabel, onToggle: toggleMobileToolbar, className: "lg:hidden" })), selectedCount > 0 && (_jsx(Div, { className: "appkit-listing-layout__bulk-bar", children: _jsx(BulkActionBar, { selectedCount: selectedCount, onClearSelection: onClearSelection, actions: bulkActionItems, labels: {
|
|
87
101
|
selected: l.filtersTitle, // placeholder — callers provide proper labels
|
|
88
102
|
apply: l.applyFilters,
|
|
89
103
|
clearSelection: l.clearAll,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef
|
|
4
|
-
import {
|
|
5
|
-
import { normalizeError } from "../../errors/normalize";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { CollapseStrip, useToolbarCollapse } from "./toolbar-collapse";
|
|
6
5
|
const TONE_CLS = {
|
|
7
6
|
default: "bg-[var(--appkit-color-surface)]",
|
|
8
7
|
muted: "bg-[var(--appkit-color-bg)]",
|
|
@@ -54,47 +53,9 @@ function resolveOffsetClass(offset) {
|
|
|
54
53
|
* height in both the expanded and collapsed states.
|
|
55
54
|
*/
|
|
56
55
|
const TOOLBAR_HEIGHT_VAR = "--appkit-toolbar-height";
|
|
57
|
-
const STORAGE_PREFIX = "appkit:sticky-toolbar-collapsed:";
|
|
58
|
-
function readCollapsed(id) {
|
|
59
|
-
if (typeof window === "undefined")
|
|
60
|
-
return false;
|
|
61
|
-
try {
|
|
62
|
-
return window.sessionStorage.getItem(STORAGE_PREFIX + id) === "1";
|
|
63
|
-
}
|
|
64
|
-
catch (_err) {
|
|
65
|
-
void normalizeError(_err);
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function writeCollapsed(id, value) {
|
|
70
|
-
try {
|
|
71
|
-
if (value)
|
|
72
|
-
window.sessionStorage.setItem(STORAGE_PREFIX + id, "1");
|
|
73
|
-
else
|
|
74
|
-
window.sessionStorage.removeItem(STORAGE_PREFIX + id);
|
|
75
|
-
}
|
|
76
|
-
catch (_err) {
|
|
77
|
-
void normalizeError(_err);
|
|
78
|
-
// sessionStorage unavailable (private browsing, etc.) — collapse still
|
|
79
|
-
// works for the current render, just doesn't survive a re-mount.
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* The collapse/expand control — one component for both states so the two
|
|
84
|
-
* halves of the affordance can't drift apart (the collapsed strip read
|
|
85
|
-
* "Show Toolbar" while the expanded one was a bare, unlabelled chevron
|
|
86
|
-
* pinned to the far right until 2026-08-24).
|
|
87
|
-
*/
|
|
88
|
-
function CollapseStrip({ collapsed, label, onToggle, className, }) {
|
|
89
|
-
return (_jsx("div", { className: `flex justify-center ${className ?? ""}`, children: _jsxs("button", { type: "button", onClick: onToggle, "aria-label": `${collapsed ? "Show" : "Hide"} ${label}`, "aria-expanded": !collapsed, className: "flex items-center gap-1 px-4 py-0.5 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]", children: [_jsx(ChevronDown, { className: `h-3.5 w-3.5 transition-transform duration-150 ${collapsed ? "" : "rotate-180"}`, "aria-hidden": "true" }), collapsed ? "Show" : "Hide", " ", label] }) }));
|
|
90
|
-
}
|
|
91
56
|
export function StickyToolbar({ children, offset = "header", tone = "translucent", border = true, padding = "toolbar", z = "above-content", role, className, dataTestId, dismissible = false, id, label = "Toolbar", }) {
|
|
92
|
-
const
|
|
57
|
+
const { collapsed, toggle } = useToolbarCollapse({ id, enabled: dismissible });
|
|
93
58
|
const rootRef = useRef(null);
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (dismissible && id)
|
|
96
|
-
setCollapsed(readCollapsed(id));
|
|
97
|
-
}, [dismissible, id]);
|
|
98
59
|
// Publish the rendered height so a bar stacked below (sticky pagination,
|
|
99
60
|
// bulk-action bar) can offset itself against what's actually on screen.
|
|
100
61
|
// `collapsed` is a dependency because the two states are distinct elements
|
|
@@ -122,12 +83,6 @@ export function StickyToolbar({ children, offset = "header", tone = "translucent
|
|
|
122
83
|
const offsetStyle = typeof offset === "number" ? { top: `${offset}px` } : undefined;
|
|
123
84
|
const borderCls = border ? "border-b border-[var(--appkit-color-border)]" : "";
|
|
124
85
|
const zCls = z === "above-toolbar" ? "z-20" : z === "above-content" ? "z-10" : "z-[5]";
|
|
125
|
-
const toggle = () => {
|
|
126
|
-
const next = !collapsed;
|
|
127
|
-
setCollapsed(next);
|
|
128
|
-
if (id)
|
|
129
|
-
writeCollapsed(id, next);
|
|
130
|
-
};
|
|
131
86
|
if (dismissible && id && collapsed) {
|
|
132
87
|
return (_jsx("div", { ref: rootRef, "data-testid": dataTestId, style: offsetStyle, className: `sticky ${offsetCls} ${zCls} ${TONE_CLS[tone]} ${borderCls}`, children: _jsx(CollapseStrip, { collapsed: true, label: label, onToggle: toggle }) }));
|
|
133
88
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tri-state on purpose: `null` means "the user has never chosen", which is
|
|
3
|
+
* what lets the viewport supply the default. A plain boolean cannot express
|
|
4
|
+
* the difference between "they expanded it" and "they have not touched it".
|
|
5
|
+
*/
|
|
6
|
+
export declare function readCollapsedPref(id: string): boolean | null;
|
|
7
|
+
/**
|
|
8
|
+
* Writes `"0"` for expanded rather than removing the key. Removing it would
|
|
9
|
+
* leave no stored preference, so on mobile the viewport default would collapse
|
|
10
|
+
* the toolbar again on the next navigation and the user's choice would read as
|
|
11
|
+
* not having stuck.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeCollapsedPref(id: string, value: boolean): void;
|
|
14
|
+
export declare function defaultCollapsedForViewport(): boolean;
|
|
15
|
+
export interface UseToolbarCollapseOptions {
|
|
16
|
+
/** Stable id — the sessionStorage key. Collapse is disabled without one. */
|
|
17
|
+
id?: string;
|
|
18
|
+
/** Set false to opt out entirely (the toolbar is always expanded). */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Force the expanded state regardless of preference. Used when the toolbar
|
|
22
|
+
* holds controls the user cannot reach any other way — `ListingLayout` keeps
|
|
23
|
+
* it open while rows are selected, since the bulk-action bar renders inside
|
|
24
|
+
* the toolbar and a collapsed one would leave no way to act on a selection.
|
|
25
|
+
*/
|
|
26
|
+
forceExpanded?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface UseToolbarCollapseResult {
|
|
29
|
+
collapsed: boolean;
|
|
30
|
+
toggle: () => void;
|
|
31
|
+
}
|
|
32
|
+
export declare function useToolbarCollapse({ id, enabled, forceExpanded, }: UseToolbarCollapseOptions): UseToolbarCollapseResult;
|
|
33
|
+
/**
|
|
34
|
+
* The collapse/expand control — one component for both states so the two
|
|
35
|
+
* halves of the affordance can't drift apart (the collapsed strip read
|
|
36
|
+
* "Show Toolbar" while the expanded one was a bare, unlabelled chevron
|
|
37
|
+
* pinned to the far right until 2026-08-24).
|
|
38
|
+
*/
|
|
39
|
+
export declare function CollapseStrip({ collapsed, label, onToggle, className, }: {
|
|
40
|
+
collapsed: boolean;
|
|
41
|
+
label: string;
|
|
42
|
+
onToggle: () => void;
|
|
43
|
+
className?: string;
|
|
44
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// appkit/src/ui/components/toolbar-collapse.tsx
|
|
4
|
+
import { useCallback, useEffect, useState } from "react";
|
|
5
|
+
import { ChevronDown } from "lucide-react";
|
|
6
|
+
import { normalizeError } from "../../errors/normalize";
|
|
7
|
+
/**
|
|
8
|
+
* The collapse mechanism shared by `<StickyToolbar dismissible>` and
|
|
9
|
+
* `<ListingLayout>`'s own mobile toolbar rows.
|
|
10
|
+
*
|
|
11
|
+
* It lives here rather than in either component because there are two
|
|
12
|
+
* unrelated toolbars on listing surfaces — the `StickyToolbar` that
|
|
13
|
+
* `ListingToolbar`/`DataListingView` mount, and `ListingLayout`'s hand-rolled
|
|
14
|
+
* `.appkit-listing-layout__toolbar` — and a second copy of "is it collapsed,
|
|
15
|
+
* and where is that remembered" would drift the first time one of them
|
|
16
|
+
* changed. One implementation, one sessionStorage key space.
|
|
17
|
+
*/
|
|
18
|
+
const STORAGE_PREFIX = "appkit:sticky-toolbar-collapsed:";
|
|
19
|
+
/** Below this the toolbar defaults to collapsed. Matches the `lg` boundary
|
|
20
|
+
* that both toolbars already use to switch between their mobile and desktop
|
|
21
|
+
* rows, so the default flips at the same width the layout does. */
|
|
22
|
+
const MOBILE_QUERY = "(max-width: 1023px)";
|
|
23
|
+
/**
|
|
24
|
+
* Tri-state on purpose: `null` means "the user has never chosen", which is
|
|
25
|
+
* what lets the viewport supply the default. A plain boolean cannot express
|
|
26
|
+
* the difference between "they expanded it" and "they have not touched it".
|
|
27
|
+
*/
|
|
28
|
+
export function readCollapsedPref(id) {
|
|
29
|
+
if (typeof window === "undefined")
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const raw = window.sessionStorage.getItem(STORAGE_PREFIX + id);
|
|
33
|
+
if (raw === null)
|
|
34
|
+
return null;
|
|
35
|
+
return raw === "1";
|
|
36
|
+
}
|
|
37
|
+
catch (_err) {
|
|
38
|
+
void normalizeError(_err);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Writes `"0"` for expanded rather than removing the key. Removing it would
|
|
44
|
+
* leave no stored preference, so on mobile the viewport default would collapse
|
|
45
|
+
* the toolbar again on the next navigation and the user's choice would read as
|
|
46
|
+
* not having stuck.
|
|
47
|
+
*/
|
|
48
|
+
export function writeCollapsedPref(id, value) {
|
|
49
|
+
try {
|
|
50
|
+
window.sessionStorage.setItem(STORAGE_PREFIX + id, value ? "1" : "0");
|
|
51
|
+
}
|
|
52
|
+
catch (_err) {
|
|
53
|
+
void normalizeError(_err);
|
|
54
|
+
// sessionStorage unavailable (private browsing, etc.) — collapsing still
|
|
55
|
+
// works for the current render, it just doesn't survive a re-mount.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function defaultCollapsedForViewport() {
|
|
59
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return window.matchMedia(MOBILE_QUERY).matches;
|
|
63
|
+
}
|
|
64
|
+
export function useToolbarCollapse({ id, enabled = true, forceExpanded = false, }) {
|
|
65
|
+
// Always starts expanded so the server and the first client render agree;
|
|
66
|
+
// the effect below corrects it after mount. Reading sessionStorage or
|
|
67
|
+
// matchMedia in the initialiser would be a hydration mismatch.
|
|
68
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!enabled || !id)
|
|
71
|
+
return;
|
|
72
|
+
const stored = readCollapsedPref(id);
|
|
73
|
+
setCollapsed(stored ?? defaultCollapsedForViewport());
|
|
74
|
+
}, [enabled, id]);
|
|
75
|
+
const toggle = useCallback(() => {
|
|
76
|
+
setCollapsed((prev) => {
|
|
77
|
+
const next = !prev;
|
|
78
|
+
if (id)
|
|
79
|
+
writeCollapsedPref(id, next);
|
|
80
|
+
return next;
|
|
81
|
+
});
|
|
82
|
+
}, [id]);
|
|
83
|
+
return { collapsed: enabled && !forceExpanded ? collapsed : false, toggle };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The collapse/expand control — one component for both states so the two
|
|
87
|
+
* halves of the affordance can't drift apart (the collapsed strip read
|
|
88
|
+
* "Show Toolbar" while the expanded one was a bare, unlabelled chevron
|
|
89
|
+
* pinned to the far right until 2026-08-24).
|
|
90
|
+
*/
|
|
91
|
+
export function CollapseStrip({ collapsed, label, onToggle, className, }) {
|
|
92
|
+
return (_jsx("div", { className: `flex justify-center ${className ?? ""}`, children: _jsxs("button", { type: "button", onClick: onToggle, "aria-label": `${collapsed ? "Show" : "Hide"} ${label}`, "aria-expanded": !collapsed, className: "flex items-center gap-1 px-4 py-0.5 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]", children: [_jsx(ChevronDown, { className: `h-3.5 w-3.5 transition-transform duration-150 ${collapsed ? "" : "rotate-180"}`, "aria-hidden": "true" }), collapsed ? "Show" : "Hide", " ", label] }) }));
|
|
93
|
+
}
|