@vendure/dashboard 3.5.0-minor-202510031341 → 3.5.0-minor-202510161257
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/plugin/dashboard.plugin.js +1 -1
- package/dist/plugin/default-page.html +1 -1
- package/dist/vite/utils/ast-utils.spec.js +3 -3
- package/dist/vite/utils/tsconfig-utils.js +2 -1
- package/dist/vite/vite-plugin-hmr.d.ts +8 -0
- package/dist/vite/vite-plugin-hmr.js +34 -0
- package/dist/vite/vite-plugin-theme.js +6 -6
- package/dist/vite/vite-plugin-transform-index.js +6 -1
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
- package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
- package/package.json +18 -5
- package/src/app/app-providers.tsx +4 -1
- package/src/app/common/map-faceted-filter-fields.ts +21 -0
- package/src/app/main.tsx +3 -1
- package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
- package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
- package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +4 -8
- package/src/app/routes/_authenticated/_global-settings/utils/global-languages.ts +268 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +15 -15
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +5 -5
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
- package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +49 -0
- package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +56 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +12 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +178 -50
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +0 -11
- package/src/app/routes/_authenticated/_products/products.tsx +6 -2
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
- package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
- package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +3 -10
- package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
- package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
- package/src/app/routes/login.tsx +2 -2
- package/src/i18n/locales/ar.po +420 -289
- package/src/i18n/locales/cs.po +420 -289
- package/src/i18n/locales/de.po +420 -289
- package/src/i18n/locales/en.po +420 -289
- package/src/i18n/locales/es.po +420 -289
- package/src/i18n/locales/fa.po +420 -289
- package/src/i18n/locales/fr.po +468 -337
- package/src/i18n/locales/he.po +420 -289
- package/src/i18n/locales/hr.po +420 -289
- package/src/i18n/locales/it.po +420 -289
- package/src/i18n/locales/ja.po +420 -289
- package/src/i18n/locales/nb.po +420 -289
- package/src/i18n/locales/ne.po +420 -289
- package/src/i18n/locales/pl.po +420 -289
- package/src/i18n/locales/pt_BR.po +420 -289
- package/src/i18n/locales/pt_PT.po +420 -289
- package/src/i18n/locales/ru.po +420 -289
- package/src/i18n/locales/sv.po +420 -289
- package/src/i18n/locales/tr.po +420 -289
- package/src/i18n/locales/uk.po +420 -289
- package/src/i18n/locales/zh_Hans.po +420 -289
- package/src/i18n/locales/zh_Hant.po +420 -289
- package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
- package/src/lib/components/data-input/affixed-input.tsx +5 -2
- package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
- package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
- package/src/lib/components/data-input/customer-group-input.tsx +0 -1
- package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
- package/src/lib/components/data-input/datetime-input.tsx +27 -13
- package/src/lib/components/data-input/default-relation-input.tsx +18 -12
- package/src/lib/components/data-input/money-input.stories.tsx +88 -0
- package/src/lib/components/data-input/money-input.tsx +7 -11
- package/src/lib/components/data-input/number-input.stories.tsx +103 -0
- package/src/lib/components/data-input/number-input.tsx +16 -5
- package/src/lib/components/data-input/password-input.stories.tsx +65 -0
- package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
- package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
- package/src/lib/components/data-input/slug-input.tsx +9 -10
- package/src/lib/components/data-input/text-input.stories.tsx +52 -0
- package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
- package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
- package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
- package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
- package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
- package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
- package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
- package/src/lib/components/data-table/data-table-filter-badge.tsx +28 -14
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
- package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
- package/src/lib/components/data-table/data-table.stories.tsx +249 -0
- package/src/lib/components/data-table/data-table.tsx +39 -11
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
- package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
- package/src/lib/components/layout/generated-breadcrumbs.tsx +4 -12
- package/src/lib/components/layout/nav-user.tsx +19 -13
- package/src/lib/components/login/login-form.tsx +39 -123
- package/src/lib/components/shared/alerts.tsx +29 -17
- package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
- package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
- package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
- package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
- package/src/lib/components/shared/configurable-operation-input.tsx +1 -1
- package/src/lib/components/shared/customer-group-selector.tsx +5 -2
- package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
- package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
- package/src/lib/components/shared/facet-value-selector.tsx +130 -34
- package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
- package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
- package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
- package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
- package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
- package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
- package/src/lib/components/shared/vendure-image.tsx +6 -7
- package/src/lib/components/ui/accordion.stories.tsx +33 -0
- package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
- package/src/lib/components/ui/alert.stories.tsx +35 -0
- package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
- package/src/lib/components/ui/badge.stories.tsx +28 -0
- package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
- package/src/lib/components/ui/button.stories.tsx +38 -0
- package/src/lib/components/ui/calendar.stories.tsx +22 -0
- package/src/lib/components/ui/card.stories.tsx +28 -0
- package/src/lib/components/ui/carousel.stories.tsx +34 -0
- package/src/lib/components/ui/checkbox.stories.tsx +31 -0
- package/src/lib/components/ui/collapsible.stories.tsx +39 -0
- package/src/lib/components/ui/command.stories.tsx +44 -0
- package/src/lib/components/ui/context-menu.stories.tsx +38 -0
- package/src/lib/components/ui/dialog.stories.tsx +52 -0
- package/src/lib/components/ui/drawer.stories.tsx +50 -0
- package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
- package/src/lib/components/ui/hover-card.stories.tsx +38 -0
- package/src/lib/components/ui/input-group.tsx +148 -0
- package/src/lib/components/ui/input-otp.stories.tsx +30 -0
- package/src/lib/components/ui/input.stories.tsx +38 -0
- package/src/lib/components/ui/label.stories.tsx +24 -0
- package/src/lib/components/ui/menubar.stories.tsx +53 -0
- package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
- package/src/lib/components/ui/pagination.stories.tsx +51 -0
- package/src/lib/components/ui/password-input.stories.tsx +32 -0
- package/src/lib/components/ui/password-input.tsx +33 -0
- package/src/lib/components/ui/popover.stories.tsx +33 -0
- package/src/lib/components/ui/progress.stories.tsx +27 -0
- package/src/lib/components/ui/radio-group.stories.tsx +34 -0
- package/src/lib/components/ui/resizable.stories.tsx +32 -0
- package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
- package/src/lib/components/ui/select.stories.tsx +36 -0
- package/src/lib/components/ui/separator.stories.tsx +35 -0
- package/src/lib/components/ui/sheet.stories.tsx +50 -0
- package/src/lib/components/ui/sidebar-context.ts +16 -0
- package/src/lib/components/ui/sidebar.tsx +2 -13
- package/src/lib/components/ui/skeleton.stories.tsx +26 -0
- package/src/lib/components/ui/slider.stories.tsx +37 -0
- package/src/lib/components/ui/switch.stories.tsx +31 -0
- package/src/lib/components/ui/table.stories.tsx +52 -0
- package/src/lib/components/ui/tabs.stories.tsx +29 -0
- package/src/lib/components/ui/textarea.stories.tsx +32 -0
- package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
- package/src/lib/components/ui/toggle.stories.tsx +39 -0
- package/src/lib/components/ui/tooltip.stories.tsx +30 -0
- package/src/lib/components/ui/tooltip.tsx +2 -2
- package/src/lib/framework/alert/alert-extensions.tsx +0 -11
- package/src/lib/framework/alert/alert-item.tsx +14 -19
- package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
- package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
- package/src/lib/framework/component-registry/component-registry.tsx +3 -14
- package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
- package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +0 -2
- package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
- package/src/lib/framework/defaults.ts +9 -13
- package/src/lib/framework/extension-api/input-component-extensions.tsx +6 -1
- package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
- package/src/lib/framework/extension-api/types/alerts.ts +12 -6
- package/src/lib/framework/extension-api/types/data-table.ts +5 -2
- package/src/lib/framework/extension-api/types/layout.ts +41 -1
- package/src/lib/framework/extension-api/types/login.ts +0 -21
- package/src/lib/framework/form-engine/value-transformers.ts +8 -1
- package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +69 -57
- package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
- package/src/lib/framework/page/detail-page.stories.tsx +151 -0
- package/src/lib/framework/page/detail-page.tsx +12 -15
- package/src/lib/framework/page/list-page.stories.tsx +217 -0
- package/src/lib/framework/page/list-page.tsx +8 -1
- package/src/lib/graphql/api.ts +18 -1
- package/src/lib/graphql/graphql-env.d.ts +1 -1
- package/src/lib/hooks/use-alerts.ts +84 -0
- package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
- package/src/lib/index.ts +12 -5
- package/src/lib/providers/alerts-provider.tsx +60 -0
- package/src/lib/providers/channel-provider.tsx +1 -0
- package/src/lib/providers/theme-provider.tsx +6 -3
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
|
|
11
11
|
import { api } from '@/vdb/graphql/api.js';
|
|
12
12
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
13
|
-
import { Trans } from '@lingui/react/macro';
|
|
13
|
+
import { Trans, useLingui } from '@lingui/react/macro';
|
|
14
14
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
|
15
15
|
import { useDebounce } from '@uidotdev/usehooks';
|
|
16
16
|
import { ChevronRight, Loader2, Plus } from 'lucide-react';
|
|
@@ -141,21 +141,24 @@ const getFacetValuesForFacetDocument = graphql(`
|
|
|
141
141
|
* @since 3.4.0
|
|
142
142
|
*/
|
|
143
143
|
export function FacetValueSelector({
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
onValueSelect,
|
|
145
|
+
disabled,
|
|
146
|
+
placeholder,
|
|
147
|
+
pageSize = 10,
|
|
148
|
+
}: FacetValueSelectorProps) {
|
|
149
149
|
const [open, setOpen] = useState(false);
|
|
150
150
|
const [searchTerm, setSearchTerm] = useState('');
|
|
151
151
|
const [expandedFacetId, setExpandedFacetId] = useState<string | null>(null);
|
|
152
|
+
const [browseMode, setBrowseMode] = useState(false);
|
|
152
153
|
const debouncedSearch = useDebounce(searchTerm, 200);
|
|
154
|
+
const { t } = useLingui();
|
|
155
|
+
const minSearchLength = 1;
|
|
153
156
|
|
|
154
157
|
// Query for facet values based on search
|
|
155
158
|
const { data: facetValueData, isLoading: isLoadingFacetValues } = useQuery({
|
|
156
159
|
queryKey: ['facetValues', debouncedSearch],
|
|
157
160
|
queryFn: () => {
|
|
158
|
-
if (debouncedSearch.length <
|
|
161
|
+
if (debouncedSearch.length < minSearchLength) {
|
|
159
162
|
return { facetValues: { items: [], totalItems: 0 } };
|
|
160
163
|
}
|
|
161
164
|
return api.query(getFacetValueListDocument, {
|
|
@@ -167,14 +170,14 @@ export function FacetValueSelector({
|
|
|
167
170
|
},
|
|
168
171
|
});
|
|
169
172
|
},
|
|
170
|
-
enabled: debouncedSearch.length >=
|
|
173
|
+
enabled: debouncedSearch.length >= minSearchLength && !expandedFacetId,
|
|
171
174
|
});
|
|
172
175
|
|
|
173
|
-
// Query for facets based on search
|
|
174
|
-
const { data:
|
|
176
|
+
// Query for facets based on search (use regular query for search, infinite for browse)
|
|
177
|
+
const { data: facetSearchData, isLoading: isLoadingFacetSearch } = useQuery({
|
|
175
178
|
queryKey: ['facets', debouncedSearch],
|
|
176
179
|
queryFn: () => {
|
|
177
|
-
if (debouncedSearch.length <
|
|
180
|
+
if (debouncedSearch.length < minSearchLength) {
|
|
178
181
|
return { facets: { items: [], totalItems: 0 } };
|
|
179
182
|
}
|
|
180
183
|
return api.query(getFacetListDocument, {
|
|
@@ -186,7 +189,36 @@ export function FacetValueSelector({
|
|
|
186
189
|
},
|
|
187
190
|
});
|
|
188
191
|
},
|
|
189
|
-
enabled: debouncedSearch.length >=
|
|
192
|
+
enabled: !browseMode && debouncedSearch.length >= minSearchLength && !expandedFacetId,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Infinite query for browse mode
|
|
196
|
+
const {
|
|
197
|
+
data: facetBrowseData,
|
|
198
|
+
isLoading: isLoadingFacetBrowse,
|
|
199
|
+
fetchNextPage: fetchNextFacetsPage,
|
|
200
|
+
hasNextPage: hasNextFacetsPage,
|
|
201
|
+
isFetchingNextPage: isFetchingNextFacetsPage,
|
|
202
|
+
} = useInfiniteQuery({
|
|
203
|
+
queryKey: ['facets', 'browse'],
|
|
204
|
+
queryFn: async ({ pageParam = 0 }) => {
|
|
205
|
+
const response = await api.query(getFacetListDocument, {
|
|
206
|
+
options: {
|
|
207
|
+
filter: {},
|
|
208
|
+
sort: { name: 'ASC' },
|
|
209
|
+
skip: pageParam * pageSize,
|
|
210
|
+
take: pageSize,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
return response.facets;
|
|
214
|
+
},
|
|
215
|
+
getNextPageParam: (lastPage, allPages) => {
|
|
216
|
+
if (!lastPage) return undefined;
|
|
217
|
+
const totalFetched = allPages.length * pageSize;
|
|
218
|
+
return totalFetched < lastPage.totalItems ? allPages.length : undefined;
|
|
219
|
+
},
|
|
220
|
+
enabled: browseMode && !expandedFacetId,
|
|
221
|
+
initialPageParam: 0,
|
|
190
222
|
});
|
|
191
223
|
|
|
192
224
|
// Query for paginated values of a specific facet when expanded
|
|
@@ -220,7 +252,9 @@ export function FacetValueSelector({
|
|
|
220
252
|
});
|
|
221
253
|
|
|
222
254
|
const facetValues = facetValueData?.facetValues.items ?? [];
|
|
223
|
-
const facets =
|
|
255
|
+
const facets = browseMode
|
|
256
|
+
? (facetBrowseData?.pages.flatMap(page => page?.items ?? []) ?? [])
|
|
257
|
+
: (facetSearchData?.facets.items ?? []);
|
|
224
258
|
const expandedFacetValues = expandedFacetData?.pages.flatMap(page => page?.items ?? []) ?? [];
|
|
225
259
|
const expandedFacetName = expandedFacetValues[0]?.facet.name;
|
|
226
260
|
|
|
@@ -237,14 +271,22 @@ export function FacetValueSelector({
|
|
|
237
271
|
{},
|
|
238
272
|
);
|
|
239
273
|
|
|
240
|
-
const isLoading =
|
|
274
|
+
const isLoading =
|
|
275
|
+
isLoadingFacetValues || isLoadingFacetSearch || isLoadingFacetBrowse || isLoadingExpandedFacet;
|
|
241
276
|
|
|
242
277
|
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
|
243
278
|
const target = e.currentTarget;
|
|
244
279
|
const scrolledToBottom = Math.abs(target.scrollHeight - target.clientHeight - target.scrollTop) < 1;
|
|
245
280
|
|
|
246
|
-
if (scrolledToBottom &&
|
|
247
|
-
|
|
281
|
+
if (scrolledToBottom && !isFetchingNextPage) {
|
|
282
|
+
// For expanded facet values
|
|
283
|
+
if (expandedFacetId && hasNextPage) {
|
|
284
|
+
void fetchNextPage();
|
|
285
|
+
}
|
|
286
|
+
// For browse mode facets
|
|
287
|
+
if (browseMode && !expandedFacetId && hasNextFacetsPage) {
|
|
288
|
+
void fetchNextFacetsPage();
|
|
289
|
+
}
|
|
248
290
|
}
|
|
249
291
|
};
|
|
250
292
|
|
|
@@ -259,22 +301,59 @@ export function FacetValueSelector({
|
|
|
259
301
|
<PopoverContent className="p-0 w-[400px]" align="start">
|
|
260
302
|
<Command shouldFilter={false}>
|
|
261
303
|
<CommandInput
|
|
262
|
-
placeholder={placeholder}
|
|
304
|
+
placeholder={placeholder ?? t`Search facet values...`}
|
|
263
305
|
value={searchTerm}
|
|
264
306
|
onValueChange={value => {
|
|
265
307
|
setSearchTerm(value);
|
|
266
308
|
setExpandedFacetId(null);
|
|
309
|
+
setBrowseMode(false);
|
|
310
|
+
}}
|
|
311
|
+
onKeyDown={e => {
|
|
312
|
+
if (
|
|
313
|
+
e.key === 'ArrowDown' &&
|
|
314
|
+
!browseMode &&
|
|
315
|
+
debouncedSearch.length < minSearchLength
|
|
316
|
+
) {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
setBrowseMode(true);
|
|
319
|
+
}
|
|
267
320
|
}}
|
|
268
321
|
disabled={disabled}
|
|
269
322
|
/>
|
|
270
323
|
<CommandList className="h-[200px] overflow-y-auto" onScroll={handleScroll}>
|
|
271
324
|
<CommandEmpty>
|
|
272
|
-
{debouncedSearch.length < 2 ? (
|
|
273
|
-
<
|
|
325
|
+
{debouncedSearch.length < 2 && !browseMode ? (
|
|
326
|
+
<div className="flex flex-col items-center gap-2 py-4">
|
|
327
|
+
<div className="text-sm text-muted-foreground">
|
|
328
|
+
<Trans>Type at least 2 characters to search...</Trans>
|
|
329
|
+
</div>
|
|
330
|
+
<Button
|
|
331
|
+
variant="outline"
|
|
332
|
+
size="sm"
|
|
333
|
+
onClick={() => setBrowseMode(true)}
|
|
334
|
+
type="button"
|
|
335
|
+
>
|
|
336
|
+
<Trans>Browse facets</Trans>
|
|
337
|
+
</Button>
|
|
338
|
+
</div>
|
|
274
339
|
) : isLoading ? (
|
|
275
340
|
<Trans>Loading...</Trans>
|
|
276
341
|
) : (
|
|
277
|
-
<
|
|
342
|
+
<div className="flex flex-col items-center gap-2 py-4">
|
|
343
|
+
<div className="text-sm text-muted-foreground">
|
|
344
|
+
<Trans>No results found</Trans>
|
|
345
|
+
</div>
|
|
346
|
+
{!browseMode && (
|
|
347
|
+
<Button
|
|
348
|
+
variant="outline"
|
|
349
|
+
size="sm"
|
|
350
|
+
onClick={() => setBrowseMode(true)}
|
|
351
|
+
type="button"
|
|
352
|
+
>
|
|
353
|
+
<Trans>Browse facets</Trans>
|
|
354
|
+
</Button>
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
278
357
|
)}
|
|
279
358
|
</CommandEmpty>
|
|
280
359
|
|
|
@@ -282,7 +361,10 @@ export function FacetValueSelector({
|
|
|
282
361
|
<>
|
|
283
362
|
<CommandGroup>
|
|
284
363
|
<CommandItem
|
|
285
|
-
onSelect={() =>
|
|
364
|
+
onSelect={() => {
|
|
365
|
+
setExpandedFacetId(null);
|
|
366
|
+
setBrowseMode(false);
|
|
367
|
+
}}
|
|
286
368
|
className="cursor-pointer"
|
|
287
369
|
>
|
|
288
370
|
← <Trans>Back to search</Trans>
|
|
@@ -297,6 +379,7 @@ export function FacetValueSelector({
|
|
|
297
379
|
onValueSelect(facetValue);
|
|
298
380
|
setSearchTerm('');
|
|
299
381
|
setExpandedFacetId(null);
|
|
382
|
+
setBrowseMode(false);
|
|
300
383
|
setOpen(false);
|
|
301
384
|
}}
|
|
302
385
|
>
|
|
@@ -318,19 +401,31 @@ export function FacetValueSelector({
|
|
|
318
401
|
) : (
|
|
319
402
|
<>
|
|
320
403
|
{facets.length > 0 && (
|
|
321
|
-
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
404
|
+
<>
|
|
405
|
+
<CommandGroup heading={<Trans>Facets</Trans>}>
|
|
406
|
+
{facets.map(facet => (
|
|
407
|
+
<CommandItem
|
|
408
|
+
key={facet.id}
|
|
409
|
+
value={`facet-${facet.id}`}
|
|
410
|
+
onSelect={() => setExpandedFacetId(facet.id)}
|
|
411
|
+
className="cursor-pointer"
|
|
412
|
+
>
|
|
413
|
+
<span className="flex-1">{facet.name}</span>
|
|
414
|
+
<ChevronRight className="h-4 w-4" />
|
|
415
|
+
</CommandItem>
|
|
416
|
+
))}
|
|
417
|
+
</CommandGroup>
|
|
418
|
+
{browseMode && isFetchingNextFacetsPage && (
|
|
419
|
+
<div className="flex items-center justify-center py-2">
|
|
420
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
{browseMode && !hasNextFacetsPage && facets.length > 0 && (
|
|
424
|
+
<div className="text-center py-2 text-sm text-muted-foreground">
|
|
425
|
+
<Trans>No more facets</Trans>
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</>
|
|
334
429
|
)}
|
|
335
430
|
|
|
336
431
|
{Object.entries(facetGroups).map(
|
|
@@ -343,6 +438,7 @@ export function FacetValueSelector({
|
|
|
343
438
|
onSelect={() => {
|
|
344
439
|
onValueSelect(facetValue);
|
|
345
440
|
setSearchTerm('');
|
|
441
|
+
setBrowseMode(false);
|
|
346
442
|
setOpen(false);
|
|
347
443
|
}}
|
|
348
444
|
>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Money } from '@/vdb/components/data-display/money.js';
|
|
2
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
3
|
+
import { FullWidthPageBlock, Page, PageLayout } from '@/vdb/framework/layout-engine/page-layout.js';
|
|
4
|
+
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
5
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import { withDescription } from '../../../../.storybook/with-description.js';
|
|
9
|
+
import { PaginatedListDataTable } from './paginated-list-data-table.js';
|
|
10
|
+
|
|
11
|
+
// GraphQL query for products list
|
|
12
|
+
const productsListDocument = graphql(`
|
|
13
|
+
query ProductsList($options: ProductListOptions) {
|
|
14
|
+
products(options: $options) {
|
|
15
|
+
items {
|
|
16
|
+
id
|
|
17
|
+
createdAt
|
|
18
|
+
updatedAt
|
|
19
|
+
name
|
|
20
|
+
slug
|
|
21
|
+
description
|
|
22
|
+
enabled
|
|
23
|
+
featuredAsset {
|
|
24
|
+
id
|
|
25
|
+
preview
|
|
26
|
+
}
|
|
27
|
+
variants {
|
|
28
|
+
id
|
|
29
|
+
name
|
|
30
|
+
sku
|
|
31
|
+
price
|
|
32
|
+
priceWithTax
|
|
33
|
+
currencyCode
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
totalItems
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`);
|
|
40
|
+
|
|
41
|
+
const meta = {
|
|
42
|
+
title: 'Components/PaginatedListDataTable',
|
|
43
|
+
component: PaginatedListDataTable,
|
|
44
|
+
...withDescription(import.meta.url, './paginated-list-data-table.js'),
|
|
45
|
+
parameters: {
|
|
46
|
+
layout: 'fullscreen',
|
|
47
|
+
},
|
|
48
|
+
tags: ['autodocs'],
|
|
49
|
+
} satisfies Meta<typeof PaginatedListDataTable>;
|
|
50
|
+
|
|
51
|
+
export default meta;
|
|
52
|
+
type Story = StoryObj<typeof meta>;
|
|
53
|
+
|
|
54
|
+
export const Playground: Story = {
|
|
55
|
+
render: () => {
|
|
56
|
+
const [page, setPage] = useState(1);
|
|
57
|
+
const [pageSize, setPageSize] = useState(10);
|
|
58
|
+
const [sorting, setSorting] = useState<SortingState>([{ id: 'name', desc: false }]);
|
|
59
|
+
const [filters, setFilters] = useState<ColumnFiltersState>([]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="p-6">
|
|
63
|
+
<Page pageId="test-page">
|
|
64
|
+
<PageLayout>
|
|
65
|
+
<FullWidthPageBlock blockId="test-block">
|
|
66
|
+
<PaginatedListDataTable
|
|
67
|
+
listQuery={productsListDocument}
|
|
68
|
+
defaultVisibility={{
|
|
69
|
+
id: false,
|
|
70
|
+
updatedAt: false,
|
|
71
|
+
description: false,
|
|
72
|
+
slug: false,
|
|
73
|
+
variants: false,
|
|
74
|
+
}}
|
|
75
|
+
customizeColumns={{
|
|
76
|
+
name: {
|
|
77
|
+
header: 'Product Name',
|
|
78
|
+
cell: ({ cell, row }) => {
|
|
79
|
+
const value = cell.getValue() as string;
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex items-center gap-2">
|
|
82
|
+
{row.original.featuredAsset && (
|
|
83
|
+
<img
|
|
84
|
+
src={row.original.featuredAsset.preview}
|
|
85
|
+
alt={value}
|
|
86
|
+
className="w-8 h-8 rounded object-cover"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
<span className="font-medium">{value}</span>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
enabled: {
|
|
95
|
+
header: 'Status',
|
|
96
|
+
cell: ({ cell }) => {
|
|
97
|
+
const value = cell.getValue() as boolean;
|
|
98
|
+
return (
|
|
99
|
+
<Badge variant={value ? 'default' : 'secondary'}>
|
|
100
|
+
{value ? 'Enabled' : 'Disabled'}
|
|
101
|
+
</Badge>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
slug: {
|
|
106
|
+
header: 'Slug',
|
|
107
|
+
cell: ({ cell }) => {
|
|
108
|
+
const value = cell.getValue() as string;
|
|
109
|
+
return (
|
|
110
|
+
<code className="text-xs bg-muted px-1 py-0.5 rounded">
|
|
111
|
+
{value}
|
|
112
|
+
</code>
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}}
|
|
117
|
+
additionalColumns={{
|
|
118
|
+
variantCount: {
|
|
119
|
+
header: 'Variants',
|
|
120
|
+
accessorFn: row => row.variants?.length ?? 0,
|
|
121
|
+
cell: ({ cell }) => {
|
|
122
|
+
const count = cell.getValue() as number;
|
|
123
|
+
return <span className="text-muted-foreground">{count}</span>;
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
price: {
|
|
127
|
+
header: 'Price Range',
|
|
128
|
+
accessorFn: row => {
|
|
129
|
+
const variants = row.variants ?? [];
|
|
130
|
+
if (variants.length === 0) return null;
|
|
131
|
+
const prices = variants.map(v => v.price);
|
|
132
|
+
const min = Math.min(...prices);
|
|
133
|
+
const max = Math.max(...prices);
|
|
134
|
+
return { min, max, currency: variants[0].currencyCode };
|
|
135
|
+
},
|
|
136
|
+
cell: ({ cell }) => {
|
|
137
|
+
const value = cell.getValue() as {
|
|
138
|
+
min: number;
|
|
139
|
+
max: number;
|
|
140
|
+
currency: string;
|
|
141
|
+
} | null;
|
|
142
|
+
if (!value) return null;
|
|
143
|
+
if (value.min === value.max) {
|
|
144
|
+
return <Money value={value.min} currency={value.currency} />;
|
|
145
|
+
}
|
|
146
|
+
return (
|
|
147
|
+
<span>
|
|
148
|
+
<Money value={value.min} currency={value.currency} /> -{' '}
|
|
149
|
+
<Money value={value.max} currency={value.currency} />
|
|
150
|
+
</span>
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
meta: {
|
|
154
|
+
dependencies: ['variants'],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
}}
|
|
158
|
+
defaultColumnOrder={[
|
|
159
|
+
'name',
|
|
160
|
+
'slug',
|
|
161
|
+
'enabled',
|
|
162
|
+
'variantCount',
|
|
163
|
+
'price',
|
|
164
|
+
'createdAt',
|
|
165
|
+
]}
|
|
166
|
+
onSearchTermChange={searchTerm => {
|
|
167
|
+
if (!searchTerm) return {};
|
|
168
|
+
return {
|
|
169
|
+
name: { contains: searchTerm },
|
|
170
|
+
};
|
|
171
|
+
}}
|
|
172
|
+
page={page}
|
|
173
|
+
itemsPerPage={pageSize}
|
|
174
|
+
sorting={sorting}
|
|
175
|
+
columnFilters={filters}
|
|
176
|
+
onPageChange={(_, page, perPage) => {
|
|
177
|
+
setPage(page);
|
|
178
|
+
setPageSize(perPage);
|
|
179
|
+
}}
|
|
180
|
+
onSortChange={(_, sorting) => {
|
|
181
|
+
setSorting(sorting);
|
|
182
|
+
}}
|
|
183
|
+
onFilterChange={(_, filters) => {
|
|
184
|
+
setFilters(filters);
|
|
185
|
+
}}
|
|
186
|
+
facetedFilters={{
|
|
187
|
+
enabled: {
|
|
188
|
+
title: 'Status',
|
|
189
|
+
options: [
|
|
190
|
+
{ label: 'Enabled', value: true },
|
|
191
|
+
{ label: 'Disabled', value: false },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
}}
|
|
195
|
+
rowActions={[
|
|
196
|
+
{
|
|
197
|
+
label: 'Edit',
|
|
198
|
+
onClick: row => console.log('Edit product:', row.original.id),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
label: 'Duplicate',
|
|
202
|
+
onClick: row => console.log('Duplicate product:', row.original.id),
|
|
203
|
+
},
|
|
204
|
+
]}
|
|
205
|
+
/>
|
|
206
|
+
</FullWidthPageBlock>
|
|
207
|
+
</PageLayout>
|
|
208
|
+
</Page>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
},
|
|
212
|
+
};
|
|
@@ -394,17 +394,6 @@ export function PaginatedListDataTable<
|
|
|
394
394
|
return { ...acc, [field]: direction };
|
|
395
395
|
}, {});
|
|
396
396
|
|
|
397
|
-
const filter = columnFilters?.length
|
|
398
|
-
? {
|
|
399
|
-
_and: columnFilters.map(f => {
|
|
400
|
-
if (Array.isArray(f.value)) {
|
|
401
|
-
return { [f.id]: { in: f.value } };
|
|
402
|
-
}
|
|
403
|
-
return { [f.id]: f.value };
|
|
404
|
-
}),
|
|
405
|
-
}
|
|
406
|
-
: undefined;
|
|
407
|
-
|
|
408
397
|
function refetchPaginatedList() {
|
|
409
398
|
queryClient.invalidateQueries({ queryKey });
|
|
410
399
|
}
|
|
@@ -438,6 +427,17 @@ export function PaginatedListDataTable<
|
|
|
438
427
|
}));
|
|
439
428
|
const minimalListQuery = includeOnlySelectedListFields(extendedListQuery, visibleColumns);
|
|
440
429
|
|
|
430
|
+
const filter = columnFilters?.length
|
|
431
|
+
? {
|
|
432
|
+
_and: columnFilters.map(f => {
|
|
433
|
+
if (Array.isArray(f.value)) {
|
|
434
|
+
return { [f.id]: { in: f.value } };
|
|
435
|
+
}
|
|
436
|
+
return { [f.id]: f.value };
|
|
437
|
+
}),
|
|
438
|
+
}
|
|
439
|
+
: undefined;
|
|
440
|
+
|
|
441
441
|
const defaultQueryKey = [
|
|
442
442
|
PaginatedListDataTableKey,
|
|
443
443
|
minimalListQuery,
|
|
@@ -456,7 +456,7 @@ export function PaginatedListDataTable<
|
|
|
456
456
|
const mergedFilter = { ...filter, ...searchFilter };
|
|
457
457
|
const variables = {
|
|
458
458
|
options: {
|
|
459
|
-
take: itemsPerPage,
|
|
459
|
+
take: Math.min(itemsPerPage, 100),
|
|
460
460
|
skip: (page - 1) * itemsPerPage,
|
|
461
461
|
sort,
|
|
462
462
|
filter: mergedFilter,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { withDescription } from '../../../../.storybook/with-description.js';
|
|
4
|
+
import { PermissionGuard } from './permission-guard.js';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/PermissionGuard',
|
|
8
|
+
component: PermissionGuard,
|
|
9
|
+
...withDescription(import.meta.url, './permission-guard.js'),
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
} satisfies Meta<typeof PermissionGuard>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
export const Playground: Story = {
|
|
20
|
+
render: () => {
|
|
21
|
+
return (
|
|
22
|
+
<div className="space-y-6">
|
|
23
|
+
<div className="space-y-2">
|
|
24
|
+
<div className="text-sm font-medium">
|
|
25
|
+
Has Permission (ReadCatalog) - Button should be visible
|
|
26
|
+
</div>
|
|
27
|
+
<PermissionGuard requires={['ReadCatalog']}>
|
|
28
|
+
<Button>View Catalog</Button>
|
|
29
|
+
</PermissionGuard>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div className="space-y-2">
|
|
33
|
+
<div className="text-sm font-medium">
|
|
34
|
+
No Permission (NonExistentPermission) - Button should be hidden
|
|
35
|
+
</div>
|
|
36
|
+
<PermissionGuard requires={['NonExistentPermission']}>
|
|
37
|
+
<Button variant="destructive">Delete Catalog</Button>
|
|
38
|
+
</PermissionGuard>
|
|
39
|
+
<div className="text-xs text-muted-foreground">
|
|
40
|
+
(The delete button is hidden because user lacks NonExistentPermission permission)
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -4,6 +4,7 @@ import { toast } from 'sonner';
|
|
|
4
4
|
|
|
5
5
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
6
6
|
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
7
|
+
import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
|
|
7
8
|
import { ResultOf } from '@/vdb/graphql/graphql.js';
|
|
8
9
|
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
9
10
|
import { Trans, useLingui } from '@lingui/react/macro';
|
|
@@ -86,6 +87,7 @@ export function RemoveFromChannelBulkAction({
|
|
|
86
87
|
}
|
|
87
88
|
icon={LayersIcon}
|
|
88
89
|
className="text-warning"
|
|
90
|
+
disabled={activeChannel.code === DEFAULT_CHANNEL_CODE}
|
|
89
91
|
/>
|
|
90
92
|
);
|
|
91
93
|
}
|
|
@@ -83,6 +83,10 @@ export function ResponsiveToolbar({ editor, disabled }: Readonly<ResponsiveToolb
|
|
|
83
83
|
[],
|
|
84
84
|
);
|
|
85
85
|
|
|
86
|
+
const canUndo = !!editor?.can().undo();
|
|
87
|
+
const canRedo = !!editor?.can().redo();
|
|
88
|
+
const canInsertTable = !!editor?.can().insertTable();
|
|
89
|
+
|
|
86
90
|
const toolbarItems: ToolbarItem[] = useMemo(() => {
|
|
87
91
|
if (!editor) return [];
|
|
88
92
|
|
|
@@ -272,7 +276,7 @@ export function ResponsiveToolbar({ editor, disabled }: Readonly<ResponsiveToolb
|
|
|
272
276
|
.run()
|
|
273
277
|
}
|
|
274
278
|
className={`h-8 px-2 ${editor.isActive('table') ? 'bg-accent' : ''}`}
|
|
275
|
-
disabled={disabled || !
|
|
279
|
+
disabled={disabled || !canInsertTable}
|
|
276
280
|
>
|
|
277
281
|
<TableIcon className="h-4 w-4" />
|
|
278
282
|
</Button>
|
|
@@ -290,7 +294,7 @@ export function ResponsiveToolbar({ editor, disabled }: Readonly<ResponsiveToolb
|
|
|
290
294
|
variant="ghost"
|
|
291
295
|
size="sm"
|
|
292
296
|
onClick={() => editor.chain().focus().undo().run()}
|
|
293
|
-
disabled={disabled || !
|
|
297
|
+
disabled={disabled || !canUndo}
|
|
294
298
|
className="h-8 px-2"
|
|
295
299
|
>
|
|
296
300
|
<Undo2Icon className="h-4 w-4" />
|
|
@@ -309,7 +313,7 @@ export function ResponsiveToolbar({ editor, disabled }: Readonly<ResponsiveToolb
|
|
|
309
313
|
variant="ghost"
|
|
310
314
|
size="sm"
|
|
311
315
|
onClick={() => editor.chain().focus().redo().run()}
|
|
312
|
-
disabled={disabled || !
|
|
316
|
+
disabled={disabled || !canRedo}
|
|
313
317
|
className="h-8 px-2"
|
|
314
318
|
>
|
|
315
319
|
<Redo2Icon className="h-4 w-4" />
|
|
@@ -317,7 +321,7 @@ export function ResponsiveToolbar({ editor, disabled }: Readonly<ResponsiveToolb
|
|
|
317
321
|
),
|
|
318
322
|
},
|
|
319
323
|
];
|
|
320
|
-
}, [editor, disabled, linkDialogOpen, imageDialogOpen]);
|
|
324
|
+
}, [editor, disabled, linkDialogOpen, imageDialogOpen, canUndo, canRedo, canInsertTable]);
|
|
321
325
|
|
|
322
326
|
useEffect(() => {
|
|
323
327
|
const calculateVisibleItems = () => {
|
|
@@ -5,6 +5,7 @@ import { TextStyle } from '@tiptap/extension-text-style';
|
|
|
5
5
|
import { EditorContent, useEditor } from '@tiptap/react';
|
|
6
6
|
import StarterKit from '@tiptap/starter-kit';
|
|
7
7
|
import { useLayoutEffect, useRef } from 'react';
|
|
8
|
+
|
|
8
9
|
import { ResponsiveToolbar } from './responsive-toolbar.js';
|
|
9
10
|
import { TableDeleteMenu } from './table-delete-menu.js';
|
|
10
11
|
import { TableEditIcons } from './table-edit-icons.js';
|