From 621cf31cb4354976987b5404a1f1548f5cd71cbd Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Fri, 22 May 2026 10:19:39 +0600 Subject: [PATCH 1/3] feat(theme): support custom className on ThemeProvider portals Add optional `className` to ThemeContextValue and thread it through every Base-UI portal wrapper (alert-dialog, combobox, dialog, dropdown-menu, modal, popover, select, sheet, tooltip) so consumers can scope plugin-ui styles to a custom root class. Also fall back to a default placeholder in SettingsSidebar search. --- src/components/settings/settings-sidebar.tsx | 2 +- src/components/ui/alert-dialog.tsx | 2 +- src/components/ui/combobox.tsx | 2 +- src/components/ui/dialog.tsx | 2 +- src/components/ui/dropdown-menu.tsx | 2 +- src/components/ui/modal.tsx | 8 ++++---- src/components/ui/popover.tsx | 2 +- src/components/ui/select.tsx | 2 +- src/components/ui/sheet.tsx | 2 +- src/components/ui/tooltip.tsx | 2 +- src/providers/theme-provider.tsx | 4 +++- 11 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/settings/settings-sidebar.tsx b/src/components/settings/settings-sidebar.tsx index c42b345..3ea764e 100644 --- a/src/components/settings/settings-sidebar.tsx +++ b/src/components/settings/settings-sidebar.tsx @@ -169,7 +169,7 @@ export function SettingsSidebar({ value={search} onChange={(e) => setSearch(e.target.value)} className="h-8 pl-8" - placeholder={searchPlaceholder} + placeholder={searchPlaceholder || 'Search...'} aria-label="Search settings" data-testid="settings-search" /> diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx index ad5cff1..e232f0b 100644 --- a/src/components/ui/alert-dialog.tsx +++ b/src/components/ui/alert-dialog.tsx @@ -48,7 +48,7 @@ function AlertDialogContent({ const mode = theme?.mode ?? 'light'; const cssVariables = theme?.cssVariables ?? defaultCssVariables; return ( - + + + + { if (portalContainer) { - portalContainer.className = cn("pui-root", mode); + portalContainer.className = cn("pui-root", mode, theme?.className); Object.assign(portalContainer.style, cssVariables); } - }, [mode, cssVariables, portalContainer]); + }, [mode, cssVariables, portalContainer, theme?.className]); // Handle escape key useEffect(() => { diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index cedaace..8904be5 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -32,7 +32,7 @@ function PopoverContent({ const mode = theme?.mode ?? 'light'; const cssVariables = theme?.cssVariables ?? defaultCssVariables; return ( - + + + + ; + className?: string; } const ThemeContext = createContext(null); @@ -497,8 +498,9 @@ export function ThemeProvider({ tokens: mergedTokens, resolvedMode, cssVariables, + className, }), - [pluginId, mode, setMode, mergedTokens, resolvedMode, cssVariables], + [pluginId, mode, setMode, mergedTokens, resolvedMode, cssVariables, className], ); return ( From 2d54d2d6e923457c19e7aad45e85156b1fcd0c21 Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Fri, 22 May 2026 10:30:17 +0600 Subject: [PATCH 2/3] build: emit JS for each subpath export Add multi-entry webpack config so `./components/ui`, `./components/settings`, `./providers`, `./themes`, and `./utils` resolve to real JS files. Repoint the `./utils` export at `dist/lib/utils.*` (where `cn` already lives) rather than a nonexistent `dist/utils/` path. Note: each entry is a self-contained CJS bundle, so mixing subpath imports with the root barrel can duplicate modules and break React context identity. Pick one import style per consumer. --- package.json | 6 +++--- webpack.config.js | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index de04e5a..c6289b0 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "require": "./dist/themes/index.js" }, "./utils": { - "types": "./dist/utils/index.d.ts", - "import": "./dist/utils/index.js", - "require": "./dist/utils/index.js" + "types": "./dist/lib/utils.d.ts", + "import": "./dist/lib/utils.js", + "require": "./dist/lib/utils.js" }, "./settings": { "types": "./dist/components/settings/index.d.ts", diff --git a/webpack.config.js b/webpack.config.js index 7aff54f..f37bfba 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,11 @@ module.exports = { ...defaultConfig, entry: { index: './src/index.ts', + 'components/ui/index': './src/components/ui/index.ts', + 'components/settings/index': './src/components/settings/index.tsx', + 'providers/index': './src/providers/index.ts', + 'themes/index': './src/themes/index.ts', + 'lib/utils': './src/lib/utils.ts', }, output: { ...defaultConfig.output, From 9d7f864708980c1d0ce463cecf4d9a1460db2dff Mon Sep 17 00:00:00 2001 From: Kamruzzaman Date: Fri, 22 May 2026 11:26:41 +0600 Subject: [PATCH 3/3] feat(dataviews): support confirmTone for non-destructive confirm actions Adds `confirmTone` ('destructive' | 'positive' | 'default') to action config so the confirm button color matches intent. Defaults to 'destructive' for backward compatibility. --- src/components/ui/index.ts | 2 +- .../wordpress/DataViews.stories.tsx | 99 +++++++++++++++++++ src/components/wordpress/dataviews.tsx | 20 +++- src/index.ts | 2 + 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index ba1872e..b02ee34 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -290,7 +290,7 @@ export { FieldContent, FieldTitle, } from "./field"; -export { DataViews, type DataViewAction, type DataViewField, type DataViewFilterField, type DataViewFilterProps, type DataViewLayouts, type DataViewsProps, type DataViewState } from '../wordpress/dataviews'; +export { DataViews, type ConfirmActionTone, type DataViewAction, type DataViewField, type DataViewFilterField, type DataViewFilterProps, type DataViewLayouts, type DataViewsProps, type DataViewState, type DestructiveActionConfig } from '../wordpress/dataviews'; export { DataForm, useFormValidity } from '@wordpress/dataviews/wp'; export type { DataFormProps, diff --git a/src/components/wordpress/DataViews.stories.tsx b/src/components/wordpress/DataViews.stories.tsx index 7de544a..0cee6d9 100644 --- a/src/components/wordpress/DataViews.stories.tsx +++ b/src/components/wordpress/DataViews.stories.tsx @@ -375,6 +375,105 @@ Both support bulk selection. Try selecting multiple rows and using the bulk tool }, }; +/** + * Confirm-button color controlled by `confirmTone`: + * - `destructive` (default) — red button for delete/remove + * - `positive` — green button for approve/publish + * - `default` — neutral primary button + */ +export const ConfirmActionTones: StoryFn = () => { + const [view, setView] = useState(createDefaultView()); + const [selection, setSelection] = useState([]); + const [users, setUsers] = useState(allUsers); + + const paginatedData = paginateData(users, view); + + const tonedActions: DataViewAction[] = [ + { + id: "approve", + label: "Approve", + icon: , + isDestructive: true, + confirmTone: "positive", + confirmTitle: "Approve users?", + confirmMessage: "Approved users gain access immediately.", + confirmButtonLabel: "Approve", + supportsBulk: true, + callback: async (items) => { + await new Promise((r) => setTimeout(r, 1000)); + setUsers((prev) => + prev.map((u) => + items.some((i) => i.id === u.id) ? { ...u, status: "active" } : u + ) + ); + setSelection([]); + }, + }, + { + id: "mark-reviewed", + label: "Mark Reviewed", + icon: , + isDestructive: true, + confirmTone: "default", + confirmTitle: "Mark as reviewed?", + confirmMessage: "Logs the review timestamp. Reversible.", + confirmButtonLabel: "Mark Reviewed", + supportsBulk: true, + callback: async (items) => { + await new Promise((r) => setTimeout(r, 800)); + alert(`Marked reviewed: ${items.map((i) => i.name).join(", ")}`); + }, + }, + { + id: "delete", + label: "Delete", + icon: , + isDestructive: true, + supportsBulk: true, + callback: async (items) => { + await new Promise((r) => setTimeout(r, 1000)); + const ids = new Set(items.map((i) => i.id)); + setUsers((prev) => prev.filter((u) => !ids.has(u.id))); + setSelection([]); + }, + }, + ]; + + return ( +
+ + namespace="dataviews-demo" + data={paginatedData} + fields={fields} + view={view} + onChangeView={setView} + actions={tonedActions} + selection={selection} + onChangeSelection={setSelection} + paginationInfo={{ + totalItems: users.length, + totalPages: getTotalPages(users.length, view.perPage), + }} + getItemId={(item) => item.id} + /> +
+ ); +}; +ConfirmActionTones.storyName = "Confirm Action Tones"; +ConfirmActionTones.parameters = { + docs: { + description: { + story: `Set \`confirmTone\` on any \`isDestructive\` action to color the confirm button. + +- **Approve** → \`confirmTone: "positive"\` (green / success) +- **Mark Reviewed** → \`confirmTone: "default"\` (neutral primary) +- **Delete** → omitted, defaults to \`"destructive"\` (red) + +Useful for non-destructive actions that still need a confirmation step.`, + }, + }, +}; + /** DataViews with dynamic filters. Click "Add Filter" to add filters. */ export const TabAndFilters: StoryFn = () => { const [view, setView] = useState(createDefaultView()); diff --git a/src/components/wordpress/dataviews.tsx b/src/components/wordpress/dataviews.tsx index 291167f..28091ce 100644 --- a/src/components/wordpress/dataviews.tsx +++ b/src/components/wordpress/dataviews.tsx @@ -58,9 +58,13 @@ function applyFiltersToTableElements( } // Extended action type with automatic destructive confirmation support +export type ConfirmActionTone = 'destructive' | 'positive' | 'default'; + export type DestructiveActionConfig = { /** When true, shows an AlertDialog confirmation before executing the action callback. */ isDestructive?: boolean; + /** Tone of the confirm button. Defaults to 'destructive'. Use 'positive' for approvals, 'default' for neutral confirmations. */ + confirmTone?: ConfirmActionTone; /** Custom title for the confirmation dialog. Defaults to the action label. */ confirmTitle?: string; /** Custom message for the confirmation dialog. */ @@ -71,6 +75,15 @@ export type DestructiveActionConfig = { cancelButtonLabel?: string; }; +const CONFIRM_TONE_VARIANT: Record< + ConfirmActionTone, + React.ComponentProps['variant'] +> = { + destructive: 'destructive', + positive: 'success', + default: 'default' +}; + // Re-export types from @wordpress/dataviews with prefixed names to avoid conflicts export type DataViewAction = Action & DestructiveActionConfig; export type { Field as DataViewField, SupportedLayouts as DataViewLayouts, View as DataViewState }; @@ -1017,7 +1030,12 @@ export function DataViews(props: DataViewsProps) { __('Cancel', 'default')} {isConfirming && } diff --git a/src/index.ts b/src/index.ts index 15260bc..c0e94a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -290,7 +290,9 @@ export { type CurrencyInputProps, type CurrencyOption, type CheckboxProps, + type ConfirmActionTone, type DataViewAction, + type DestructiveActionConfig, type DataViewField, type DataViewFilterField, type DataViewFilterProps,