Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b7298c4
feat: implement SubscriptionTable scaffolding and register global mod…
abhinavkdeval08-design Jun 29, 2026
812e1ac
feat: implement SubscriptionTable scaffolding and register global mod…
abhinavkdeval08-design Jun 29, 2026
43af05b
refactor: resolve bot feedback for SubscriptionTable
abhinavkdeval08-design Jul 6, 2026
7d6b915
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Jul 6, 2026
9ed80d4
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Jul 7, 2026
f8c8be8
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Jul 14, 2026
1b53031
Merge branch 'master' into feature/subscription-comparison-table
hortison Jul 15, 2026
ccda099
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Jul 15, 2026
06e9bb9
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Jul 16, 2026
0034b96
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Jul 23, 2026
55cfd16
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Aug 29, 2026
191ae3b
refactor: resolve bot feedback on responsive overflow, tokens, and ac…
abhinavkdeval08-design Aug 29, 2026
d1d1835
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Sep 1, 2026
efd53ac
feat: add root exports, default export and unit tests for Subscriptio…
abhinavkdeval08-design Sep 1, 2026
111f843
Merge branch 'master' into feature/subscription-comparison-table
abhinavkdeval08-design Sep 26, 2026
171f8be
refactor(SubscriptionTable): switch to standard typography variants a…
abhinavkdeval08-design Sep 26, 2026
26b15e0
refactor(SubscriptionTable): simplify custom barrel export and assert…
abhinavkdeval08-design Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/custom/SubscriptionTable/SubscriptionTable.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { SubscriptionTable, PlanFeature } from './SubscriptionTable';

const mockFeatures: PlanFeature[] = [
{
featureName: 'Cloud Storage',
freePlan: '5GB',
teamPlan: '50GB',
enterprisePlan: 'Unlimited'
},
{
featureName: 'Priority Support',
freePlan: false,
teamPlan: true,
enterprisePlan: true
}
];

describe('SubscriptionTable Component', () => {
it('renders table title and feature rows correctly', () => {
render(<SubscriptionTable title="Custom Pricing Table" features={mockFeatures} />);
expect(screen.getByText('Custom Pricing Table')).toBeInTheDocument();
expect(screen.getByText('Cloud Storage')).toBeInTheDocument();
expect(screen.getByText('Priority Support')).toBeInTheDocument();
expect(screen.getByText('5GB')).toBeInTheDocument();
});

it('renders boolean indicators with accessibility labels', () => {
render(<SubscriptionTable features={mockFeatures} />);
// Assert actual accessible text labels instead of test IDs
expect(screen.getAllByLabelText('Included').length).toBeGreaterThan(0);
expect(screen.getAllByLabelText('Not included').length).toBeGreaterThan(0);
});

it('calls onPlanSelect when action buttons are clicked', () => {
const handlePlanSelect = jest.fn();
render(<SubscriptionTable features={mockFeatures} onPlanSelect={handlePlanSelect} />);

fireEvent.click(screen.getByText('Get Started'));
expect(handlePlanSelect).toHaveBeenCalledWith('free');

fireEvent.click(screen.getByText('Upgrade'));
expect(handlePlanSelect).toHaveBeenCalledWith('team');

fireEvent.click(screen.getByText('Contact Us'));
expect(handlePlanSelect).toHaveBeenCalledWith('enterprise');
});

it('renders custom labels when passed via props', () => {
render(
<SubscriptionTable
features={mockFeatures}
featuresLabel="Key Capabilities"
freePlanLabel="Starter"
freePlanButtonLabel="Join Now"
/>
);
expect(screen.getByText('Key Capabilities')).toBeInTheDocument();
expect(screen.getByText('Starter')).toBeInTheDocument();
expect(screen.getByText('Join Now')).toBeInTheDocument();
});
});
145 changes: 145 additions & 0 deletions src/custom/SubscriptionTable/SubscriptionTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import { Box, Button, Table, TableBody, TableHead, TableRow, Typography } from '@mui/material';
import React from 'react';
Comment thread
abhinavkdeval08-design marked this conversation as resolved.
import { FeatureHeaderCell, StyledHeaderRow, StyledTableCell, StyledTableContainer } from './style';

export interface PlanFeature {
featureName: string;
freePlan: boolean | string;
teamPlan: boolean | string;
enterprisePlan: boolean | string;
}

export interface SubscriptionTableProps {
title?: string;
features?: PlanFeature[];
onPlanSelect?: (planType: 'free' | 'team' | 'enterprise') => void;
featuresLabel?: string;
freePlanLabel?: string;
freePlanButtonLabel?: string;
teamPlanLabel?: string;
teamPlanButtonLabel?: string;
enterprisePlanLabel?: string;
enterprisePlanButtonLabel?: string;
}

export const SubscriptionTable: React.FC<SubscriptionTableProps> = ({
title = 'Subscription Plans Comparison',
features = [],
onPlanSelect,
featuresLabel = 'Features',
freePlanLabel = 'Free Plan',
freePlanButtonLabel = 'Get Started',
teamPlanLabel = 'Team Plan',
teamPlanButtonLabel = 'Upgrade',
enterprisePlanLabel = 'Enterprise Plan',
enterprisePlanButtonLabel = 'Contact Us'
}) => {
Comment thread
abhinavkdeval08-design marked this conversation as resolved.
const renderValue = (value: boolean | string) => {
if (typeof value === 'boolean') {
return value ? (
<CheckIcon color="success" data-testid="check-icon" titleAccess="Included" aria-label="Included" />
) : (
<CloseIcon color="error" data-testid="close-icon" titleAccess="Not included" aria-label="Not included" />
);
}
return (
<Typography variant="body2" fontWeight="fontWeightMedium">
{value}
</Typography>
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

return (
<Box sx={{ width: '100%', my: 4 }}>
{title && (
<Typography
variant="h4"
component="h2"
sx={{
mb: 3,
fontWeight: 'fontWeightBold'
}}
>
{title}
</Typography>
)}
<StyledTableContainer>
<Table sx={{ minWidth: 650 }} aria-label="subscription comparison table">
<TableHead>
<StyledHeaderRow>
<StyledTableCell>
<Typography variant="subtitle1" fontWeight="fontWeightBold" component="span">
{featuresLabel}
</Typography>
</StyledTableCell>
<StyledTableCell align="center">
<Typography variant="subtitle1" fontWeight="fontWeightBold" component="span">
{freePlanLabel}
</Typography>
<Box sx={{ mt: 1 }}>
<Button size="small" variant="outlined" onClick={() => onPlanSelect?.('free')}>
{freePlanButtonLabel}
</Button>
</Box>
</StyledTableCell>
<StyledTableCell align="center">
<Typography variant="subtitle1" fontWeight="fontWeightBold" component="span">
{teamPlanLabel}
</Typography>
<Box sx={{ mt: 1 }}>
<Button
size="small"
variant="contained"
color="primary"
onClick={() => onPlanSelect?.('team')}
>
{teamPlanButtonLabel}
</Button>
</Box>
</StyledTableCell>
<StyledTableCell align="center">
<Typography variant="subtitle1" fontWeight="fontWeightBold" component="span">
{enterprisePlanLabel}
</Typography>
<Box sx={{ mt: 1 }}>
<Button
size="small"
variant="contained"
color="secondary"
onClick={() => onPlanSelect?.('enterprise')}
>
{enterprisePlanButtonLabel}
</Button>
</Box>
</StyledTableCell>
</StyledHeaderRow>
</TableHead>
<TableBody>
{features.map((row) => (
<TableRow
key={row.featureName}
sx={{
'&:last-child td, &:last-child th': { border: 0 },
'&:hover': { backgroundColor: 'action.hover' }
}}
>
<FeatureHeaderCell component="th" scope="row">
{row.featureName}
</FeatureHeaderCell>
<StyledTableCell align="center">{renderValue(row.freePlan)}</StyledTableCell>
<StyledTableCell align="center">{renderValue(row.teamPlan)}</StyledTableCell>
<StyledTableCell align="center">{renderValue(row.enterprisePlan)}</StyledTableCell>
</TableRow>
))}
</TableBody>
</Table>
</StyledTableContainer>
</Box>
);
};

SubscriptionTable.displayName = 'SubscriptionTable';

export default SubscriptionTable;
1 change: 1 addition & 0 deletions src/custom/SubscriptionTable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SubscriptionTable';
26 changes: 26 additions & 0 deletions src/custom/SubscriptionTable/style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Paper, TableCell, TableRow } from '@mui/material';
import { styled } from '@mui/material/styles';

export const StyledTableContainer = styled(Paper)(({ theme }) => ({
borderRadius: theme.shape.borderRadius * 2,
overflowX: 'auto',
overflowY: 'hidden',
boxShadow: theme.shadows[2],
border: `1px solid ${theme.palette.divider}`
}));
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const StyledHeaderRow = styled(TableRow)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'light' ? theme.palette.grey[50] : theme.palette.grey[900]
}));

export const StyledTableCell = styled(TableCell)(({ theme }) => ({
fontFamily: theme.typography.fontFamily,
borderColor: theme.palette.divider,
fontWeight: 500
}));

export const FeatureHeaderCell = styled(StyledTableCell)(({ theme }) => ({
fontFamily: theme.typography.fontFamily,
fontWeight: 700,
color: theme.palette.text.primary
}));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
3 changes: 2 additions & 1 deletion src/custom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export * from './Markdown';
export * from './Modal';
export * from './RJSFFormWrapper';
export * from './StyledAccordion';
export * from './SubscriptionTable';
export * from './WidgetPicker';
export * from './WidgetEmptyState';
export * from './WidgetEmptyState';
1 change: 1 addition & 0 deletions src/custom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,6 @@ export * from './permissions';
export * from './ResourceDetailFormatters';
export * from './RJSFFormWrapper';
export * from './ShareModal';
export * from './SubscriptionTable';
export * from './UserSearchField';
export * from './Workspaces';
8 changes: 8 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,11 @@ export {
type Team as TeamPickerRecord,
type TeamSearchFieldProps
} from './custom/DashboardWidgets/GettingStartedWidget/TeamSearchField';

// Explicit re-export for SubscriptionTable to prevent rollup-plugin-dts from
// dropping the component and its types from the generated dist/index.d.ts bundle.
export {
SubscriptionTable,
type SubscriptionTableProps,
type PlanFeature
} from './custom/SubscriptionTable';
Loading