@adobe-commerce/aio-experience-kit
v1.0.4
Published
A comprehensive TypeScript toolkit for building Adobe Commerce Admin UI extensions with React Spectrum components. Provides production-ready components and utilities for Adobe I/O extensibility platform integration.
Readme
@adobe-commerce/aio-experience-kit
Overview
A TypeScript toolkit for building Adobe Commerce Admin UI extensions with React Spectrum components. This library provides production-ready components and utilities that integrate seamlessly with Adobe's I/O extensibility platform.
Key Benefits:
- Rapid Development: Pre-built components reduce development time from weeks to days
- Adobe Design Consistency: All components follow Adobe Spectrum design principles
- Enterprise Ready: Built-in accessibility, internationalization, and performance optimizations
- Developer Experience: Full TypeScript support with intelligent code completion
- Extensible Architecture: Modular design allows for easy customization and extension
- Production Tested: Battle-tested components used in real Adobe Commerce extensions
Installation
Install the TypeScript library with custom React components using React Spectrum:
npm install @adobe-commerce/aio-experience-kitUsage
The toolkit is organized into two main modules:
⚛️ React Components
A collection of specialized React components built on Adobe React Spectrum, designed for creating consistent and accessible admin interfaces.
MainContainer
The main layout component for Adobe Commerce admin extensions. Provides flexible layouts with integrated navigation, routing, and responsive design.
import { MainContainer, LayoutOptions, NavigationOptions } from '@adobe-commerce/aio-experience-kit';
import { Text } from '@adobe/react-spectrum';
import HomeIcon from "@spectrum-icons/workflow/Home"
import BookIcon from "@spectrum-icons/workflow/Book"
const LibraryApp = (props) => {
const navigationButtons = [
{
label: "Home",
path: "/",
icon: <HomeIcon size={"S"} gridArea="Home" />
}, {
label: "Books",
path: "/book/grid",
icon: <BookIcon size={"S"} gridArea="Books" />
}
];
const appRoutes = [
{
paths: ['/'],
component: <Text>Home</Text>
}, {
paths: ['/book/:component/', '/book/:component/:id'],
component: <Text>Books</Text>
}
];
return (
<MainContainer
title="Library Application"
titleLevel={1}
buttons={navigationButtons}
routes={appRoutes}
navigation={NavigationOptions.NavigationButtons}
layout={LayoutOptions.OneColumn}
/>
);
};Features:
- Flexible Layouts: Choose between OneColumn for focused content or TwoColumnLeft for navigation-heavy interfaces
- Smart Navigation: Integrated navigation system supporting both link-based and button-based patterns
- Routing Integration: Seamless React Router integration with automatic route matching and component rendering
- Responsive Design: Automatically adapts to different screen sizes while maintaining usability
- Customizable Spacing: Fine-grained control over padding, margins, and component spacing
- Accessibility First: Built-in keyboard navigation, screen reader support, and focus management
DataTable
A powerful data grid component for displaying and managing tabular data with advanced features like sorting, selection, and bulk operations.
import React, { useState } from 'react';
import { View, ActionButton, Text } from '@adobe/react-spectrum';
import { DataTable, useRouteParams } from '@adobe-commerce/aio-experience-kit';
import AddCircle from '@spectrum-icons/workflow/AddCircle';
const EntityGrid = ({ actionCallHeaders }) => {
const { getNavigate } = useRouteParams();
const navigate = getNavigate();
const [isProcessing, setProcessing] = useState(false);
const [gridData, setGridData] = useState([]);
const entityModel = new EntityModel(actionCallHeaders);
const columns = entityModel.columns();
const buttons = [
<ActionButton
variant="primary"
type="button"
marginEnd="size-150"
isDisabled={isProcessing}
onPress={() => {
navigate('/entity/form');
}}
>
<AddCircle gridArea="Create Entity" />
<Text>Create Entity</Text>
</ActionButton>
];
const gridActions = [
{ key: 'edit', text: 'Edit' },
{ key: 'delete', text: 'Delete' }
];
const massActions = [
{ key: 'delete', text: 'Delete' }
];
return (
<DataTable
columns={columns}
data={gridData}
buttons={buttons}
gridActions={gridActions}
massActions={massActions}
isProcessing={isProcessing}
onMassActionPress={async (key, ids) => {
switch (key) {
case 'delete':
// Process mass delete
break;
}
}}
onGridActionPress={async (key, item) => {
switch (key) {
case 'edit':
navigate(`/entity/form/${item.id}`);
break;
case 'delete':
// Process single delete
break;
}
}}
onGridLoad={async () => {
setGridData(await entityModel.list());
}}
/>
);
};
// Entity Model
class EntityModel {
constructor(actionCallHeaders) {
this.actionCallHeaders = actionCallHeaders;
}
async list() {
// Load data using API
return [];
}
columns() {
return [
{ name: 'Name', uid: 'name' },
{ name: 'Status', uid: 'status' },
{ name: 'Created At', uid: 'created_at' },
{ name: 'Updated At', uid: 'updated_at' },
{ name: 'Actions', uid: 'actions' }
];
}
}Vertical Scrolling for Large Datasets (v1.0.3+):
Control table height with automatic scrolling:
<DataTable
columns={columns}
data={largeDataset}
maxHeight="size-6000" // Spectrum token (480px)
// or maxHeight="500px" // CSS value
// or maxHeight="60vh" // Viewport height
onGridLoad={async () => {
// Load large dataset
}}
/>Search and Filtering Capabilities:
Add built-in search functionality to enable users to quickly find and filter records within your DataTable. The search bar is configurable and supports both button-triggered and search-as-you-type modes.
Example 1: Basic Search (Button Click Mode)
<DataTable
columns={columns}
data={data}
searchConfig={{
enabled: true,
placeholder: "Search products...",
searchOnType: false, // Only search when button clicked or Enter pressed
}}
onSearch={async (searchText) => {
// Triggered only on button click or Enter key
await fetchFilteredData(searchText);
}}
/>Example 2: Search-as-you-type with Debounce (Recommended for Client-Side Filtering)
const [allData, setAllData] = useState(products);
const [filteredData, setFilteredData] = useState(products);
<DataTable
columns={columns}
data={filteredData} // Show filtered results
searchConfig={{
enabled: true,
searchOnType: true, // Search while typing
debounceMs: 300, // Wait 300ms after user stops typing
showClearButton: true,
minCharacters: 2, // Only search if 2+ characters entered
}}
onSearch={async (searchText) => {
if (!searchText) {
setFilteredData(allData); // Show all if search is empty
return;
}
// Client-side filtering
const filtered = allData.filter(item =>
Object.values(item).some(val =>
String(val).toLowerCase().includes(searchText.toLowerCase())
)
);
setFilteredData(filtered);
}}
/>Example 3: Server-Side Search with Loading State
const [isSearching, setSearching] = useState(false);
<DataTable
columns={columns}
data={data}
searchConfig={{
enabled: true,
isSearching: isSearching, // Shows spinner in search button
placeholder: "Search across 10,000+ records...",
searchOnType: true,
debounceMs: 800, // Longer debounce for server calls
}}
onSearch={async (searchText) => {
setSearching(true);
try {
const results = await apiSearchCall(searchText);
setData(results);
} finally {
setSearching(false);
}
}}
/>Example 4: Column-Specific Search
<DataTable
columns={columns}
data={data}
searchConfig={{
enabled: true,
searchableColumns: ['name', 'sku', 'email'], // Only search these columns
caseSensitive: false,
}}
onSearch={async (searchText, searchColumns) => {
// searchColumns will be ['name', 'sku', 'email']
await searchInColumns(searchText, searchColumns);
}}
/>Search Configuration Options:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| enabled | boolean | false | Enable/disable search bar |
| placeholder | string | "Search..." | Placeholder text for search input |
| searchButtonLabel | string | "Search" | Label text displayed on search button (shown with icon) |
| clearButtonLabel | string | "Clear search" | Accessible label for clear button |
| showClearButton | boolean | true | Show clear button when text exists |
| searchOnType | boolean | false | Search while typing vs. button click only |
| debounceMs | number | 300 | Debounce time for search-as-you-type (ms) |
| caseSensitive | boolean | false | Case-sensitive search |
| searchableColumns | string[] | [] | Specific columns to search (empty = all columns) |
| minCharacters | number | 1 | Minimum characters before search triggers |
| maxLength | number | 100 | Maximum length of search input |
| showResultsCount | boolean | false | Show count of filtered results |
| isSearching | boolean | false | Show loading state in search button |
| ariaLabel | string | "Search table" | ARIA label for search input |
Search Behavior Modes:
Button Mode (
searchOnType: false): Search only triggers when the search button is clicked or Enter key is pressed. Ideal for server-side searches with expensive operations.Type Mode (
searchOnType: true): Search triggers as the user types (with debouncing). Perfect for client-side filtering or real-time search experiences.
Keyboard Shortcuts:
- Enter: Triggers search in both modes (bypasses debounce in type mode)
- Escape: Clears search when
showClearButtonis enabled
Accessibility Features:
- Full keyboard navigation support
- Customizable ARIA labels for screen readers
- Loading state announcements
- High contrast mode support
Features:
- Advanced Data Management: Sortable columns with intelligent data type handling and custom sort functions
- Flexible Selection: Support for single-row selection or multi-row selection with visual feedback
- Bulk Operations: Mass action system for performing operations on multiple selected items
- Contextual Actions: Per-row actions with customizable button layouts, links, or dropdown menus
- State Management: Built-in loading states, empty state handling, and error boundary support
- Performance Optimized: Virtual scrolling for large datasets with efficient re-rendering
- Scrollable Tables: Optional maxHeight prop for vertical scrolling with large datasets (v1.0.3+)
- Search & Filter: Built-in search bar with button and type modes, column-specific filtering
- User Experience: Intuitive interactions with hover states, keyboard navigation, and clear visual hierarchy
DataForm
A dynamic form builder that creates forms from configuration objects with built-in validation and multiple field types.
import React, { useState } from 'react';
import { DataForm, useRouteParams, FieldType } from '@adobe-commerce/aio-experience-kit';
import { ToastQueue } from '@react-spectrum/toast';
const EntityForm = ({ actionCallHeaders, id = undefined }) => {
const entityModel = new EntityModel(actionCallHeaders);
const [formFields, setFormFields] = useState({});
const [editItem, setEditItem] = useState({});
const { getNavigate } = useRouteParams();
const navigate = getNavigate();
return (
<DataForm
components={formFields}
editItem={editItem}
onFormLoad={async () => {
// Load edit item first
let editItem = {};
if (id !== undefined) {
editItem = await entityModel.load(id);
setEditItem(editItem);
}
// Then load form fields
const formFields = entityModel.fields(editItem);
setFormFields(formFields);
}}
onFormSubmit={async (values) => {
try {
await entityModel.save(values);
ToastQueue.positive('Entity saved successfully', { timeout: 5000 });
} catch (e) {
ToastQueue.negative(e.message, { timeout: 5000 });
}
}}
onPostFormSubmit={() => {
navigate('/entity/grid');
}}
onBackPress={() => {
navigate('/entity/grid');
}}
/>
);
};
// Entity Model
class EntityModel {
constructor(actionCallHeaders) {
this.actionCallHeaders = actionCallHeaders;
}
async load(id) {
// Load entity using API
return {};
}
async save(formValues) {
// Save entity using API
return [];
}
fields(editItem) {
return {
groups: [
{
code: 'entity_settings',
label: 'Entity Settings',
fields: [
{
label: 'Name',
code: 'name',
db_field: 'name',
type: FieldType.TEXT,
required: true
},
{
label: 'Status',
code: 'status',
db_field: 'status',
type: FieldType.SELECT,
required: true,
options: [
{
value: '1',
label: 'Enabled'
},
{
value: '0',
label: 'Disabled'
}
]
},
{
label: 'Categories',
code: 'categories',
db_field: 'categories',
type: FieldType.MULTISELECT,
required: false,
options: [
{
value: 'electronics',
label: 'Electronics'
},
{
value: 'clothing',
label: 'Clothing'
},
{
value: 'books',
label: 'Books'
},
{
value: 'home',
label: 'Home & Garden'
}
]
},
{
label: 'Featured Product',
code: 'featured',
db_field: 'featured',
type: FieldType.TOGGLE,
required: false
},
{
label: 'Notifications',
code: 'notifications',
db_field: 'notifications',
type: FieldType.TOGGLE,
required: false,
use_env_var: true
}
]
}
]
};
}
}Field Types
The DataForm component supports various field types for different input scenarios:
Basic Input Fields:
FieldType.TEXT- Standard text inputFieldType.EMAIL- Email input with validationFieldType.PASSWORD- Password input with maskingFieldType.NUMBER- Numeric input with validationFieldType.URL- URL input with validationFieldType.TEL- Telephone number inputFieldType.SEARCH- Search input with appropriate styling
Selection Fields:
FieldType.SELECT- Single selection dropdown with optionsFieldType.MULTISELECT- Multiple selection with checkboxes in a scrollable containerFieldType.TOGGLE- Boolean toggle switch with '1'/'0' values
Display Fields:
FieldType.LABEL- Read-only display field for showing values
Field Configuration Examples:
// MULTISELECT field with scrollable options
{
label: 'Product Categories',
code: 'categories',
db_field: 'categories',
type: FieldType.MULTISELECT,
required: false,
options: [
{ value: 'electronics', label: 'Electronics' },
{ value: 'clothing', label: 'Clothing' },
{ value: 'books', label: 'Books' },
{ value: 'home', label: 'Home & Garden' },
// ... more options (automatically scrollable when many)
]
}
// TOGGLE field with automatic default value
{
label: 'Enable Feature',
code: 'feature_enabled',
db_field: 'feature_enabled',
type: FieldType.TOGGLE,
required: false
// Automatically defaults to '0' if no value provided
// Returns '1' when enabled, '0' when disabled
}
// Field with environment variable support
{
label: 'API Endpoint',
code: 'api_endpoint',
db_field: 'api_endpoint',
type: FieldType.TEXT,
required: true,
use_env_var: true // Adds checkbox to use as environment variable
}Dependent Field Visibility
The DataForm component supports conditional field visibility based on the values of other fields. This allows you to create dynamic forms that show or hide fields based on user selections.
Basic Example:
{
groups: [
{
code: 'settings',
label: 'Settings',
fields: [
{
label: 'Enable Feature',
code: 'feature_enabled',
db_field: 'feature_enabled',
type: FieldType.TOGGLE,
required: false
},
{
label: 'Feature Configuration',
code: 'feature_config',
db_field: 'feature_config',
type: FieldType.TEXT,
required: false,
// This field only shows when feature_enabled is truthy
dependsOn: {
field: 'feature_enabled',
condition: { type: 'truthy' }
}
}
]
}
]
}Dependency Condition Types:
// Equals - Show field when value matches exactly
dependsOn: {
field: 'status',
condition: { type: 'equals', value: 'active' }
}
// Not Equals - Show field when value doesn't match
dependsOn: {
field: 'status',
condition: { type: 'notEquals', value: 'disabled' }
}
// In - Show field when value is in array
dependsOn: {
field: 'status',
condition: { type: 'in', values: ['pending', 'processing'] }
}
// Not In - Show field when value is not in array
dependsOn: {
field: 'status',
condition: { type: 'notIn', values: ['cancelled', 'completed'] }
}
// Greater Than - Show field when numeric value is greater
dependsOn: {
field: 'quantity',
condition: { type: 'greaterThan', value: 10 }
}
// Less Than - Show field when numeric value is less
dependsOn: {
field: 'stock_level',
condition: { type: 'lessThan', value: 5 }
}
// Truthy - Show field when value is truthy (handles '0' and 'false' as falsy)
dependsOn: {
field: 'enable_feature',
condition: { type: 'truthy' }
}
// Falsy - Show field when value is falsy
dependsOn: {
field: 'enable_feature',
condition: { type: 'falsy' }
}
// Custom - Show field based on custom validation function
dependsOn: {
field: 'username',
condition: {
type: 'custom',
validator: (value, allFormValues) => {
return value && value.length > 3;
}
}
}Advanced Example - Multi-Step Configuration:
{
groups: [
{
code: 'shipping',
label: 'Shipping Configuration',
fields: [
{
label: 'Shipping Method',
code: 'shipping_method',
db_field: 'shipping_method',
type: FieldType.SELECT,
required: true,
options: [
{ value: 'standard', label: 'Standard Shipping' },
{ value: 'express', label: 'Express Shipping' },
{ value: 'pickup', label: 'Store Pickup' }
]
},
{
label: 'Carrier',
code: 'carrier',
db_field: 'carrier',
type: FieldType.SELECT,
required: true,
options: [
{ value: 'ups', label: 'UPS' },
{ value: 'fedex', label: 'FedEx' },
{ value: 'usps', label: 'USPS' }
],
// Only show carrier selection for standard and express shipping
dependsOn: {
field: 'shipping_method',
condition: { type: 'in', values: ['standard', 'express'] }
}
},
{
label: 'Tracking Number',
code: 'tracking_number',
db_field: 'tracking_number',
type: FieldType.TEXT,
required: false,
// Only show tracking for shipped methods
dependsOn: {
field: 'shipping_method',
condition: { type: 'notEquals', value: 'pickup' }
}
},
{
label: 'Store Location',
code: 'store_location',
db_field: 'store_location',
type: FieldType.SELECT,
required: true,
options: [
{ value: 'store1', label: 'Main Street Store' },
{ value: 'store2', label: 'Downtown Store' }
],
// Only show store location for pickup
dependsOn: {
field: 'shipping_method',
condition: { type: 'equals', value: 'pickup' }
}
}
]
}
]
}Dependency Configuration:
interface FieldDependency {
field: string; // The code of the field this depends on
condition: DependencyCondition; // The condition to evaluate
clearValueOnHide?: boolean; // Whether to clear field value when hidden (default: false)
}Key Features:
- Dynamic Visibility: Fields automatically show/hide based on dependency conditions
- Multiple Conditions: Support for equals, notEquals, in, notIn, greaterThan, lessThan, truthy, falsy, and custom validators
- Validation Integration: Hidden fields are excluded from form validation
- Value Management: Optional automatic clearing of hidden field values
- Performance Optimized: Uses React memoization to prevent unnecessary re-renders
- Chain Dependencies: Fields can depend on other dependent fields
- Form Submission: Hidden field values are automatically excluded from submission
Features:
- Dynamic Form Generation: Create complex forms from simple configuration objects without manual field creation
- Rich Field Types: Support for text, email, password, number, select, multiselect, toggle, and specialized input types
- Conditional Field Visibility: Show/hide fields dynamically based on other field values with multiple condition types
- Multi-Selection Support: MULTISELECT field type with React Spectrum CheckboxGroup for selecting multiple options with scrollable overflow and accessibility support
- Toggle Controls: TOGGLE field type using React Spectrum Switch component with '1'/'0' value handling and automatic default value initialization
- Environment Variable Support: Built-in support for environment variable checkboxes on compatible field types (TEXT, SELECT, MULTISELECT, TOGGLE)
- Intelligent Validation: Built-in validation with custom rules, real-time feedback, and error messaging
- Workflow Integration: Loading states, processing indicators, and navigation controls for multi-step workflows
- Organized Layout: Logical field grouping with collapsible sections and intuitive information hierarchy
- Developer Friendly: Event-driven architecture with lifecycle hooks for custom business logic integration
- Configurable Buttons: Full control over form action buttons with custom labels, icons, visibility, positioning, and alignment
Button Configuration
The DataForm component provides extensive button customization through the buttonConfig prop:
Basic Usage:
<DataForm
components={formFields}
editItem={editItem}
onFormLoad={onFormLoad}
onFormSubmit={onFormSubmit}
onBackPress={onBackPress}
buttonConfig={{
submitButton: { label: 'Create Product' },
backButton: { label: 'Cancel' },
}}
/>Button Configuration Options:
interface FormButtonConfig {
submitButton?: {
label?: string; // Button text (default: "Save")
icon?: ReactElement; // Custom icon (default: SaveFloppy)
isHidden?: boolean; // Hide button (default: false)
ariaLabel?: string; // Custom ARIA label
};
backButton?: {
label?: string; // Button text (default: "Back")
icon?: ReactElement; // Custom icon (default: Back)
isHidden?: boolean; // Hide button (default: false)
ariaLabel?: string; // Custom ARIA label
};
secondaryButtons?: Array<{
key: string; // Unique identifier
label: string; // Button text
icon?: ReactElement; // Optional icon
onPress: () => Promise<void>; // Click handler
variant?: 'primary' | 'secondary' | 'negative'; // Style variant
isDisabled?: boolean; // Disable state
type?: 'button' | 'submit'; // Button type (default: 'button')
ariaLabel?: string; // Custom ARIA label
}>;
position?: 'before' | 'after'; // Button position relative to form (default: 'after')
alignment?: 'start' | 'center' | 'end'; // Horizontal alignment (default: 'start')
gap?: 'small' | 'medium' | 'large'; // Spacing between buttons (default: 'small')
}Common Use Cases:
// Example 1: Create vs Edit Forms
<DataForm
buttonConfig={{
submitButton: {
label: isEditMode ? 'Update Product' : 'Create Product'
},
}}
/>
// Example 2: Modal Forms (hide back button)
<DataForm
buttonConfig={{
submitButton: { label: 'Save Changes' },
backButton: { isHidden: true },
}}
/>
// Example 3: Wizard Forms with Navigation
<DataForm
buttonConfig={{
submitButton: { label: 'Next Step' },
backButton: { label: 'Previous Step' },
}}
/>
// Example 4: Draft Workflow with Secondary Action
<DataForm
buttonConfig={{
submitButton: { label: 'Save Draft' },
secondaryButtons: [
{
key: 'publish',
label: 'Publish',
variant: 'primary',
onPress: async () => {
await handlePublish();
},
},
],
}}
/>
// Example 5: Edit Form with Delete Action
<DataForm
buttonConfig={{
submitButton: { label: 'Update' },
secondaryButtons: [
{
key: 'delete',
label: 'Delete',
variant: 'negative',
onPress: async () => {
const confirmed = await showConfirmDialog();
if (confirmed) {
await handleDelete();
navigate('/products');
}
},
},
],
}}
/>
// Example 6: Top-Aligned Buttons for Long Forms
<DataForm
buttonConfig={{
submitButton: { label: 'Save' },
position: 'before',
alignment: 'end',
}}
/>
// Example 7: Right-Aligned Buttons (Dialog Pattern)
<DataForm
buttonConfig={{
submitButton: { label: 'Confirm' },
backButton: { label: 'Cancel' },
alignment: 'end',
}}
/>
// Example 8: Multi-Action Workflow
<DataForm
buttonConfig={{
submitButton: { label: 'Save' },
secondaryButtons: [
{
key: 'save-continue',
label: 'Save & Continue',
onPress: async () => {
await handleSave();
navigateToNext();
},
},
{
key: 'save-new',
label: 'Save & Add New',
onPress: async () => {
await handleSave();
resetForm();
},
},
],
}}
/>
// Example 9: Custom Icons and ARIA Labels
import SaveFloppy from '@spectrum-icons/workflow/SaveFloppy';
import CheckmarkCircle from '@spectrum-icons/workflow/CheckmarkCircle';
<DataForm
buttonConfig={{
submitButton: {
label: 'Approve',
icon: <CheckmarkCircle />,
ariaLabel: 'Approve and save changes',
},
}}
/>
// Example 10: Centered Buttons with Custom Gap
<DataForm
buttonConfig={{
submitButton: { label: 'Submit' },
backButton: { label: 'Cancel' },
alignment: 'center',
gap: 'large',
}}
/>Button Order and States:
- Button Order: ProgressCircle → Submit → Back → Secondary buttons (in order)
- Disabled States: All buttons are automatically disabled during form submission or when
isProcessingis true - Loading States: ProgressCircle shows automatically during submission, secondary buttons can show individual loading states
- Accessibility: All buttons have proper ARIA labels and keyboard navigation support
Migration Notes:
For existing implementations without buttonConfig, the default behavior is preserved:
- Submit button labeled "Save" with SaveFloppy icon
- Back button labeled "Back" with Back icon
- Buttons positioned after the form with start (left) alignment
- No breaking changes to existing code
ConfirmationDialog
A modal dialog component for user confirmations with customizable actions and styling. Perfect for delete confirmations and critical user decisions.
import React, { useState } from 'react';
import { View, ActionButton, Text } from '@adobe/react-spectrum';
import { DataTable, useRouteParams, ConfirmationDialog } from '@adobe-commerce/aio-experience-kit';
const EntityGrid = ({ actionCallHeaders }) => {
const { getNavigate } = useRouteParams();
const navigate = getNavigate();
const [isProcessing, setProcessing] = useState(false);
const [gridData, setGridData] = useState([]);
const [deleteMessage, setDeleteMessage] = useState('');
const [deleteKeys, setDeleteKeys] = useState([]);
const entityModel = new EntityModel(actionCallHeaders);
return (
<View>
<DataTable
// ... other DataTable props
onMassActionPress={async (key, ids) => {
switch (key) {
case 'delete':
setDeleteKeys(ids);
setDeleteMessage(`Are you sure you want to delete ${ids.length} selected items?`);
break;
}
}}
onGridActionPress={async (key, item) => {
switch (key) {
case 'delete':
setDeleteKeys([item.id]);
setDeleteMessage(`Are you sure you want to delete "${item.name}"?`);
break;
}
}}
/>
<ConfirmationDialog
title="Delete Dialog"
message={deleteMessage}
primaryButtonVariant="negative"
onDismiss={() => {
setDeleteMessage('');
}}
onSecondaryPress={() => {
setDeleteMessage('');
}}
onPrimaryPress={async () => {
setDeleteMessage('');
setProcessing(true);
setGridData(await entityModel.delete(deleteKeys));
setProcessing(false);
}}
/>
</View>
);
};
// Entity Model
class EntityModel {
constructor(actionCallHeaders) {
this.actionCallHeaders = actionCallHeaders;
}
async delete(itemIds = []) {
// Delete entity using API
return [];
}
}Features:
- Flexible Content: Customizable title, message, and button text to match any confirmation scenario
- Action Variants: Support for different button styles (primary, secondary, negative) to indicate action severity
- User Experience: Intuitive keyboard navigation with escape key dismissal and tab order management
- Accessibility Excellence: Full screen reader support, proper ARIA labels, and focus trap implementation
- Design Consistency: Follows Adobe Spectrum design patterns for familiar user interactions
FileUpload
A flexible file upload component with file validation and automatic content reading. Built on React Spectrum's FileTrigger for consistent Adobe design patterns.
import React, { useState } from 'react';
import { View } from '@adobe/react-spectrum';
import { FileUpload } from '@adobe-commerce/aio-experience-kit';
import { ToastQueue } from '@react-spectrum/toast';
const DocumentUploader = () => {
const [isProcessing, setProcessing] = useState(false);
return (
<View>
<FileUpload
label="Upload Documents"
isRequired={true}
acceptedFileTypes={['application/pdf', '.doc', '.docx']}
allowsMultiple={true}
isDisabled={isProcessing}
onSelect={async (files) => {
setProcessing(true);
try {
// Process each file
} catch (error) {
ToastQueue.negative(`Upload failed: ${error.message}`, { timeout: 5000 });
} finally {
setProcessing(false);
}
}}
/>
</View>
);
};Programmatic Control with Refs (v1.0.3+):
Reset the component programmatically from parent components:
import React, { useRef } from 'react';
import { FileUpload, FileUploadHandle } from '@adobe-commerce/aio-experience-kit';
const MyForm = () => {
const fileUploadRef = useRef<FileUploadHandle>(null);
const handleReset = () => {
fileUploadRef.current?.reset(); // Clear all files
};
const handleGetFiles = () => {
const files = fileUploadRef.current?.getFiles(); // Get current files
console.log('Current files:', files);
};
return (
<>
<FileUpload
ref={fileUploadRef}
label="Upload Files"
onSelect={(files) => console.log('Selected:', files)}
/>
<Button onPress={handleReset}>Reset</Button>
<Button onPress={handleGetFiles}>Get Files</Button>
</>
);
};Features:
- File Selection: Click to browse files with support for single or multiple file selection
- Automatic Content Reading: Automatically reads file content as text or base64 based on file type
- File Type Validation: Built-in validation for accepted MIME types and file extensions
- Built-in Display: Automatically displays selected files with name, size, and type information
- Clear Functionality: Built-in "Clear Files" link to remove selected files and programmatic reset via ref
- Imperative Handle: Access reset() and getFiles() methods via React refs for parent control
- Loading States: Shows processing indicator while reading file contents
- Error Handling: Comprehensive error messaging for file reading failures
- Accessibility: Full keyboard navigation, screen reader support, and ARIA labels
- Type Safety: Complete TypeScript support with FileInfo interface for processed files
🔧 Utils
Utility functions and classes that simplify common tasks in Adobe Commerce extension development.
AdminUiSdk
A utility class for managing Adobe IMS credentials in Adobe Admin UI extensions. Provides secure access to authentication tokens and organization IDs for API calls.
Note: As of v1.0.2, registration methods have been removed. Use the Adobe UIX SDK directly for extension registration.
Getting Adobe IMS Credentials
Retrieve authentication credentials from the Adobe shared context:
import { AdminUiSdk } from '@adobe-commerce/aio-experience-kit';
try {
const sdk = new AdminUiSdk('my_admin_extension');
const credentials = await sdk.getCredentials();
if (credentials.imsToken && credentials.imsOrgId) {
// Use credentials for API calls
const apiHeaders = {
'Authorization': `Bearer ${credentials.imsToken}`,
'x-gw-ims-org-id': credentials.imsOrgId
};
// Make authenticated API requests
} else {
console.log('Credentials not available');
}
} catch (error) {
console.error('Failed to get credentials:', error.message);
}Features:
- Credential Management: Secure access to Adobe IMS authentication tokens and organization IDs
- Shared Context Access: Retrieves credentials from Adobe's shared context using @adobe/uix-guest
- Error Handling: Comprehensive error handling for credential retrieval failures
- Type Safety: Full TypeScript support with intelligent code completion and compile-time validation
- Production Ready: Battle-tested utility used in real Adobe Commerce extensions
useRouteParams
A React hook that provides a clean API for accessing React Router parameters and navigation functions.
import { useRouteParams } from '@adobe-commerce/aio-experience-kit';
const ProductDetail = () => {
const { getParam, getNavigate } = useRouteParams();
const productId = getParam('id');
const navigate = getNavigate();
const handleBack = () => {
navigate('/products');
};
return (
<div>
<h1>Product {productId}</h1>
<button onClick={handleBack}>Back to Products</button>
</div>
);
};Features:
- Parameter Access: Type-safe access to URL parameters with automatic type inference and null safety
- Navigation Control: Wrapped navigation function with programmatic routing and history management
- Clean API: Simplified interface that abstracts React Router complexity while maintaining full functionality
- Hook Integration: Seamless integration with React component lifecycle and state management patterns
- Error Handling: Built-in protection against navigation errors and invalid route parameters
- Performance Optimized: Efficient parameter extraction and navigation with minimal re-renders
TypeScript Support
This library is built with TypeScript and provides comprehensive type definitions for all components and utilities. Import types as needed:
import type {
MainContainerProps,
DataTableProps,
DataFormProps,
ConfirmationDialogProps,
FileUploadProps,
FileUploadHandle,
FieldType,
AdminUiSdkInterface,
AdminUiSdkCredentials
} from '@adobe-commerce/aio-experience-kit';
// Deprecated types (for backward compatibility only)
// These types are deprecated and will be removed in v2.0.0
// import type {
// ShippingCarrierFormProps,
// SelectOption,
// AdminUiSdkRegistration,
// MenuItem
// } from '@adobe-commerce/aio-experience-kit';License
See the LICENSE file (in package) for license rights and limitations.
