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/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 ( - + + + + { + 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, diff --git a/src/providers/theme-provider.tsx b/src/providers/theme-provider.tsx index f50cb6b..1ddc566 100644 --- a/src/providers/theme-provider.tsx +++ b/src/providers/theme-provider.tsx @@ -196,6 +196,7 @@ export interface ThemeContextValue { tokens: ThemeTokens; resolvedMode: "light" | "dark"; cssVariables: Record; + 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 ( 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,