@jmruthers/pace-core 0.5.140 → 0.5.142
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/README.md +2 -2
- package/dist/{DataTable-JXFCA2BJ.js → DataTable-SKCX4SCB.js} +6 -6
- package/dist/{EventLogo-rFL_kRjk.d.ts → EventLogo-B3V3otev.d.ts} +307 -1
- package/dist/{UnifiedAuthProvider-XIQQ7LVU.js → UnifiedAuthProvider-BMJAP6Z7.js} +3 -3
- package/dist/{chunk-22WKWKRX.js → chunk-2AKRP5QZ.js} +4 -4
- package/dist/{chunk-4C7EXCAR.js → chunk-CRGFNQ2L.js} +4 -4
- package/dist/{chunk-TLT2ZR3L.js → chunk-E6ZCVF4T.js} +4 -4
- package/dist/{chunk-INQLMHPF.js → chunk-ERGKJX4D.js} +2 -2
- package/dist/{chunk-6LAAY47Q.js → chunk-MSHEVJXS.js} +2 -2
- package/dist/{chunk-MA6EPSGZ.js → chunk-PKW27QVS.js} +2 -2
- package/dist/{chunk-T6JN6LH6.js → chunk-R53TUSFK.js} +3 -3
- package/dist/{chunk-6DXZ6V5Q.js → chunk-SFVL7ZFI.js} +5 -5
- package/dist/{chunk-5JMOHWDI.js → chunk-TUJSIWX6.js} +497 -329
- package/dist/chunk-TUJSIWX6.js.map +1 -0
- package/dist/{chunk-BOOI7GK2.js → chunk-VOJBGZYI.js} +119 -3
- package/dist/chunk-VOJBGZYI.js.map +1 -0
- package/dist/{chunk-YCWDTTUK.js → chunk-WM26XK7I.js} +22 -8
- package/dist/chunk-WM26XK7I.js.map +1 -0
- package/dist/components.d.ts +3 -1
- package/dist/components.js +20 -8
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +4 -2
- package/dist/index.js +25 -11
- package/dist/index.js.map +1 -1
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +94 -1
- package/dist/rbac/index.js +9 -7
- package/dist/utils.js +1 -1
- package/docs/api/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +40 -0
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +155 -0
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +9 -0
- package/docs/api/interfaces/TabsListProps.md +9 -0
- package/docs/api/interfaces/TabsProps.md +9 -0
- package/docs/api/interfaces/TabsTriggerProps.md +9 -0
- package/docs/api/interfaces/TextareaProps.md +53 -0
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +34 -0
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +289 -2
- package/docs/rbac/README.md +2 -1
- package/docs/rbac/event-based-apps.md +872 -0
- package/package.json +3 -1
- package/src/components/Calendar/Calendar.test.tsx +338 -0
- package/src/components/Calendar/Calendar.tsx +192 -0
- package/src/components/Calendar/index.ts +10 -0
- package/src/components/Tabs/Tabs.test.tsx +439 -0
- package/src/components/Tabs/Tabs.tsx +202 -0
- package/src/components/Tabs/index.ts +10 -0
- package/src/components/Textarea/Textarea.test.tsx +269 -0
- package/src/components/Textarea/Textarea.tsx +133 -0
- package/src/components/Textarea/index.ts +10 -0
- package/src/components/index.ts +11 -0
- package/src/index.ts +11 -0
- package/src/rbac/hooks/index.ts +2 -0
- package/src/rbac/hooks/useResourcePermissions.test.ts +633 -0
- package/src/rbac/hooks/useResourcePermissions.ts +235 -0
- package/src/services/EventService.ts +29 -8
- package/src/services/__tests__/EventService.test.ts +48 -8
- package/dist/chunk-5JMOHWDI.js.map +0 -1
- package/dist/chunk-BOOI7GK2.js.map +0 -1
- package/dist/chunk-YCWDTTUK.js.map +0 -1
- package/src/rbac/docs/event-based-apps.md +0 -285
- /package/dist/{DataTable-JXFCA2BJ.js.map → DataTable-SKCX4SCB.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-XIQQ7LVU.js.map → UnifiedAuthProvider-BMJAP6Z7.js.map} +0 -0
- /package/dist/{chunk-22WKWKRX.js.map → chunk-2AKRP5QZ.js.map} +0 -0
- /package/dist/{chunk-4C7EXCAR.js.map → chunk-CRGFNQ2L.js.map} +0 -0
- /package/dist/{chunk-TLT2ZR3L.js.map → chunk-E6ZCVF4T.js.map} +0 -0
- /package/dist/{chunk-INQLMHPF.js.map → chunk-ERGKJX4D.js.map} +0 -0
- /package/dist/{chunk-6LAAY47Q.js.map → chunk-MSHEVJXS.js.map} +0 -0
- /package/dist/{chunk-MA6EPSGZ.js.map → chunk-PKW27QVS.js.map} +0 -0
- /package/dist/{chunk-T6JN6LH6.js.map → chunk-R53TUSFK.js.map} +0 -0
- /package/dist/{chunk-6DXZ6V5Q.js.map → chunk-SFVL7ZFI.js.map} +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Textarea Component Tests
|
|
3
|
+
* @description Comprehensive tests for Textarea component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { screen } from '@testing-library/react';
|
|
8
|
+
import userEvent from '@testing-library/user-event';
|
|
9
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
10
|
+
import { Textarea } from './Textarea';
|
|
11
|
+
import { renderWithProviders } from '../../__tests__/helpers/test-utils';
|
|
12
|
+
|
|
13
|
+
describe('Textarea Component', () => {
|
|
14
|
+
// Basic rendering tests
|
|
15
|
+
describe('Rendering', () => {
|
|
16
|
+
it('renders with default props', () => {
|
|
17
|
+
renderWithProviders(<Textarea />);
|
|
18
|
+
const textarea = screen.getByRole('textbox');
|
|
19
|
+
expect(textarea).toBeInTheDocument();
|
|
20
|
+
expect(textarea.tagName).toBe('TEXTAREA');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders with custom placeholder', () => {
|
|
24
|
+
renderWithProviders(<Textarea placeholder="Enter your message..." />);
|
|
25
|
+
expect(screen.getByPlaceholderText('Enter your message...')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders with custom className', () => {
|
|
29
|
+
renderWithProviders(<Textarea className="custom-class" />);
|
|
30
|
+
const textarea = screen.getByRole('textbox');
|
|
31
|
+
expect(textarea).toHaveClass('custom-class');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders with value', () => {
|
|
35
|
+
renderWithProviders(<Textarea value="test value" />);
|
|
36
|
+
expect(screen.getByDisplayValue('test value')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders with defaultValue', () => {
|
|
40
|
+
renderWithProviders(<Textarea defaultValue="default value" />);
|
|
41
|
+
expect(screen.getByDisplayValue('default value')).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders with rows attribute', () => {
|
|
45
|
+
renderWithProviders(<Textarea rows={5} />);
|
|
46
|
+
const textarea = screen.getByRole('textbox');
|
|
47
|
+
expect(textarea).toHaveAttribute('rows', '5');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Variant tests
|
|
52
|
+
describe('Variants', () => {
|
|
53
|
+
it('renders with default variant', () => {
|
|
54
|
+
renderWithProviders(<Textarea variant="default" />);
|
|
55
|
+
const textarea = screen.getByRole('textbox');
|
|
56
|
+
expect(textarea).toHaveClass('border-input');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders with destructive variant', () => {
|
|
60
|
+
renderWithProviders(<Textarea variant="destructive" />);
|
|
61
|
+
const textarea = screen.getByRole('textbox');
|
|
62
|
+
expect(textarea).toHaveClass('border-destructive', 'focus-visible:ring-destructive');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('applies destructive styling when error is true', () => {
|
|
66
|
+
renderWithProviders(<Textarea error={true} />);
|
|
67
|
+
const textarea = screen.getByRole('textbox');
|
|
68
|
+
expect(textarea).toHaveClass('border-destructive', 'focus-visible:ring-destructive');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Size tests
|
|
73
|
+
describe('Sizes', () => {
|
|
74
|
+
it('renders with small size', () => {
|
|
75
|
+
renderWithProviders(<Textarea size="sm" />);
|
|
76
|
+
const textarea = screen.getByRole('textbox');
|
|
77
|
+
expect(textarea).toHaveClass('min-h-[60px]', 'px-2', 'py-1', 'text-xs');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('renders with medium size (default)', () => {
|
|
81
|
+
renderWithProviders(<Textarea size="md" />);
|
|
82
|
+
const textarea = screen.getByRole('textbox');
|
|
83
|
+
expect(textarea).toHaveClass('min-h-[80px]', 'px-3', 'py-2', 'text-sm');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('renders with large size', () => {
|
|
87
|
+
renderWithProviders(<Textarea size="lg" />);
|
|
88
|
+
const textarea = screen.getByRole('textbox');
|
|
89
|
+
expect(textarea).toHaveClass('min-h-[100px]', 'px-4', 'py-3', 'text-base');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Event handling tests
|
|
94
|
+
describe('Event Handling', () => {
|
|
95
|
+
it('handles onChange events', async () => {
|
|
96
|
+
const handleChange = vi.fn();
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
|
|
99
|
+
renderWithProviders(<Textarea onChange={handleChange} />);
|
|
100
|
+
|
|
101
|
+
const textarea = screen.getByRole('textbox');
|
|
102
|
+
await user.type(textarea, 'test input');
|
|
103
|
+
|
|
104
|
+
expect(handleChange).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('handles onFocus events', async () => {
|
|
108
|
+
const handleFocus = vi.fn();
|
|
109
|
+
const user = userEvent.setup();
|
|
110
|
+
|
|
111
|
+
renderWithProviders(<Textarea onFocus={handleFocus} />);
|
|
112
|
+
|
|
113
|
+
const textarea = screen.getByRole('textbox');
|
|
114
|
+
await user.click(textarea);
|
|
115
|
+
|
|
116
|
+
expect(handleFocus).toHaveBeenCalledTimes(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('handles onBlur events', async () => {
|
|
120
|
+
const handleBlur = vi.fn();
|
|
121
|
+
const user = userEvent.setup();
|
|
122
|
+
|
|
123
|
+
renderWithProviders(<Textarea onBlur={handleBlur} />);
|
|
124
|
+
|
|
125
|
+
const textarea = screen.getByRole('textbox');
|
|
126
|
+
await user.click(textarea);
|
|
127
|
+
await user.tab();
|
|
128
|
+
|
|
129
|
+
expect(handleBlur).toHaveBeenCalledTimes(1);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('handles onKeyDown events', async () => {
|
|
133
|
+
const handleKeyDown = vi.fn();
|
|
134
|
+
const user = userEvent.setup();
|
|
135
|
+
|
|
136
|
+
renderWithProviders(<Textarea onKeyDown={handleKeyDown} />);
|
|
137
|
+
|
|
138
|
+
const textarea = screen.getByRole('textbox');
|
|
139
|
+
await user.type(textarea, 'a');
|
|
140
|
+
|
|
141
|
+
expect(handleKeyDown).toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// State management tests
|
|
146
|
+
describe('State Management', () => {
|
|
147
|
+
it('handles controlled state', () => {
|
|
148
|
+
const { rerender } = renderWithProviders(
|
|
149
|
+
<Textarea value="initial" />
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(screen.getByDisplayValue('initial')).toBeInTheDocument();
|
|
153
|
+
|
|
154
|
+
rerender(<Textarea value="updated" />);
|
|
155
|
+
expect(screen.getByDisplayValue('updated')).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('handles uncontrolled state', () => {
|
|
159
|
+
renderWithProviders(<Textarea defaultValue="default" />);
|
|
160
|
+
expect(screen.getByDisplayValue('default')).toBeInTheDocument();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('handles disabled state', () => {
|
|
164
|
+
renderWithProviders(<Textarea disabled />);
|
|
165
|
+
const textarea = screen.getByRole('textbox');
|
|
166
|
+
expect(textarea).toBeDisabled();
|
|
167
|
+
expect(textarea).toHaveClass('disabled:cursor-not-allowed', 'disabled:opacity-50');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Accessibility tests
|
|
172
|
+
describe('Accessibility', () => {
|
|
173
|
+
it('has proper ARIA attributes', () => {
|
|
174
|
+
renderWithProviders(<Textarea aria-label="Test textarea" />);
|
|
175
|
+
const textarea = screen.getByRole('textbox');
|
|
176
|
+
expect(textarea).toHaveAttribute('aria-label', 'Test textarea');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('supports aria-describedby', () => {
|
|
180
|
+
renderWithProviders(
|
|
181
|
+
<div>
|
|
182
|
+
<Textarea aria-describedby="help-text" />
|
|
183
|
+
<div id="help-text">This is help text</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
const textarea = screen.getByRole('textbox');
|
|
187
|
+
expect(textarea).toHaveAttribute('aria-describedby', 'help-text');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('supports aria-invalid for error states', () => {
|
|
191
|
+
renderWithProviders(<Textarea aria-invalid="true" />);
|
|
192
|
+
const textarea = screen.getByRole('textbox');
|
|
193
|
+
expect(textarea).toHaveAttribute('aria-invalid', 'true');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('is keyboard accessible', () => {
|
|
197
|
+
renderWithProviders(<Textarea />);
|
|
198
|
+
const textarea = screen.getByRole('textbox');
|
|
199
|
+
expect(textarea).not.toHaveAttribute('tabindex', '-1');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('supports screen readers with proper focus management', async () => {
|
|
203
|
+
const user = userEvent.setup();
|
|
204
|
+
renderWithProviders(<Textarea />);
|
|
205
|
+
|
|
206
|
+
const textarea = screen.getByRole('textbox');
|
|
207
|
+
await user.click(textarea);
|
|
208
|
+
|
|
209
|
+
expect(textarea).toHaveFocus();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Error handling tests
|
|
214
|
+
describe('Error Handling', () => {
|
|
215
|
+
it('handles error state styling', () => {
|
|
216
|
+
renderWithProviders(<Textarea error={true} />);
|
|
217
|
+
const textarea = screen.getByRole('textbox');
|
|
218
|
+
expect(textarea).toHaveClass('border-destructive', 'focus-visible:ring-destructive');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Integration tests
|
|
223
|
+
describe('Integration', () => {
|
|
224
|
+
it('works with forms', async () => {
|
|
225
|
+
const handleSubmit = vi.fn((e) => e.preventDefault());
|
|
226
|
+
const user = userEvent.setup();
|
|
227
|
+
|
|
228
|
+
renderWithProviders(
|
|
229
|
+
<form onSubmit={handleSubmit}>
|
|
230
|
+
<Textarea name="test-textarea" />
|
|
231
|
+
<button type="submit">Submit</button>
|
|
232
|
+
</form>
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await user.click(screen.getByRole('button', { name: 'Submit' }));
|
|
236
|
+
expect(handleSubmit).toHaveBeenCalledTimes(1);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('works with labels', () => {
|
|
240
|
+
renderWithProviders(
|
|
241
|
+
<div>
|
|
242
|
+
<label htmlFor="test-textarea">Test Label</label>
|
|
243
|
+
<Textarea id="test-textarea" />
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const textarea = screen.getByLabelText('Test Label');
|
|
248
|
+
expect(textarea).toBeInTheDocument();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('forwards ref correctly', () => {
|
|
252
|
+
const ref = React.createRef<HTMLTextAreaElement>();
|
|
253
|
+
renderWithProviders(<Textarea ref={ref} />);
|
|
254
|
+
|
|
255
|
+
expect(ref.current).toBeInstanceOf(HTMLTextAreaElement);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('handles multi-line input', async () => {
|
|
259
|
+
const user = userEvent.setup();
|
|
260
|
+
renderWithProviders(<Textarea />);
|
|
261
|
+
|
|
262
|
+
const textarea = screen.getByRole('textbox');
|
|
263
|
+
await user.type(textarea, 'Line 1{Enter}Line 2');
|
|
264
|
+
|
|
265
|
+
expect(textarea).toHaveValue('Line 1\nLine 2');
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Textarea Component
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Textarea
|
|
5
|
+
* @since 0.5.141
|
|
6
|
+
*
|
|
7
|
+
* A multi-line text input component with consistent styling matching the Input component.
|
|
8
|
+
* Provides flexible, accessible textarea component with consistent styling and behavior.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Multiple textarea variants (default, destructive)
|
|
12
|
+
* - Multiple textarea sizes (sm, md, lg)
|
|
13
|
+
* - Error state styling
|
|
14
|
+
* - Forwarded ref support
|
|
15
|
+
* - Consistent styling with Input component
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // Basic textarea
|
|
20
|
+
* <Textarea placeholder="Enter your message..." />
|
|
21
|
+
*
|
|
22
|
+
* // Textarea with variants and sizes
|
|
23
|
+
* <Textarea
|
|
24
|
+
* variant="destructive"
|
|
25
|
+
* size="lg"
|
|
26
|
+
* placeholder="Error textarea"
|
|
27
|
+
* />
|
|
28
|
+
*
|
|
29
|
+
* // Textarea with error state
|
|
30
|
+
* <Textarea
|
|
31
|
+
* placeholder="Comments"
|
|
32
|
+
* error={true}
|
|
33
|
+
* />
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @accessibility
|
|
37
|
+
* - Proper ARIA attributes and roles
|
|
38
|
+
* - Keyboard navigation support
|
|
39
|
+
* - Screen reader friendly
|
|
40
|
+
* - Focus management
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import * as React from 'react';
|
|
44
|
+
import { cn } from '../../utils/core/cn';
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// TEXTAREA COMPONENT
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
export interface TextareaProps
|
|
51
|
+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
52
|
+
/**
|
|
53
|
+
* Textarea variant style
|
|
54
|
+
*/
|
|
55
|
+
variant?: 'default' | 'destructive';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Textarea size
|
|
59
|
+
*/
|
|
60
|
+
size?: 'sm' | 'md' | 'lg';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Error state for styling
|
|
64
|
+
*/
|
|
65
|
+
error?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Textarea component
|
|
70
|
+
* A flexible, accessible textarea component with multiple variants and sizes.
|
|
71
|
+
* Matches the Input component API and styling for consistency.
|
|
72
|
+
*
|
|
73
|
+
* @param props - Textarea configuration and styling
|
|
74
|
+
* @param ref - Forwarded ref to the textarea element
|
|
75
|
+
* @returns JSX.Element - The rendered textarea element
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* // Basic textarea
|
|
80
|
+
* <Textarea placeholder="Enter your message..." />
|
|
81
|
+
*
|
|
82
|
+
* // Textarea with error state
|
|
83
|
+
* <Textarea
|
|
84
|
+
* placeholder="Comments"
|
|
85
|
+
* error={true}
|
|
86
|
+
* />
|
|
87
|
+
*
|
|
88
|
+
* // Large textarea with destructive variant
|
|
89
|
+
* <Textarea
|
|
90
|
+
* variant="destructive"
|
|
91
|
+
* size="lg"
|
|
92
|
+
* placeholder="Error textarea"
|
|
93
|
+
* />
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
97
|
+
({ className, variant = 'default', size = 'md', error, ...props }, ref) => {
|
|
98
|
+
return (
|
|
99
|
+
<textarea
|
|
100
|
+
className={cn(
|
|
101
|
+
// Base styles (matching Input component)
|
|
102
|
+
'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
103
|
+
|
|
104
|
+
// Variant styles
|
|
105
|
+
{
|
|
106
|
+
'border-input': variant === 'default' && !error,
|
|
107
|
+
'border-destructive focus-visible:ring-destructive': variant === 'destructive' || error,
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Size styles
|
|
111
|
+
{
|
|
112
|
+
'min-h-[60px] px-2 py-1 text-xs': size === 'sm',
|
|
113
|
+
'min-h-[80px] px-3 py-2 text-sm': size === 'md',
|
|
114
|
+
'min-h-[100px] px-4 py-3 text-base': size === 'lg',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
className
|
|
118
|
+
)}
|
|
119
|
+
ref={ref}
|
|
120
|
+
{...props}
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
Textarea.displayName = 'Textarea';
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// EXPORTS
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
export { Textarea };
|
|
133
|
+
|
package/src/components/index.ts
CHANGED
|
@@ -46,6 +46,9 @@ export type { InputProps } from './Input';
|
|
|
46
46
|
export { Label } from './Label';
|
|
47
47
|
export type { LabelProps } from './Label';
|
|
48
48
|
|
|
49
|
+
export { Textarea } from './Textarea';
|
|
50
|
+
export type { TextareaProps } from './Textarea';
|
|
51
|
+
|
|
49
52
|
export { Alert, AlertTitle, AlertDescription } from './Alert';
|
|
50
53
|
export { Avatar, AvatarImage, AvatarFallback } from './Avatar';
|
|
51
54
|
|
|
@@ -114,6 +117,14 @@ export {
|
|
|
114
117
|
SelectSeparator,
|
|
115
118
|
} from './Select';
|
|
116
119
|
|
|
120
|
+
// Tabs exports
|
|
121
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs';
|
|
122
|
+
export type { TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps } from './Tabs';
|
|
123
|
+
|
|
124
|
+
// Calendar exports
|
|
125
|
+
export { Calendar } from './Calendar';
|
|
126
|
+
export type { CalendarProps } from './Calendar';
|
|
127
|
+
|
|
117
128
|
// Toast exports
|
|
118
129
|
export {
|
|
119
130
|
Toast,
|
package/src/index.ts
CHANGED
|
@@ -76,6 +76,9 @@ export type { InputProps } from './components/Input/Input';
|
|
|
76
76
|
export { Label } from './components/Label/Label';
|
|
77
77
|
export type { LabelProps } from './components/Label/Label';
|
|
78
78
|
|
|
79
|
+
export { Textarea } from './components/Textarea/Textarea';
|
|
80
|
+
export type { TextareaProps } from './components/Textarea/Textarea';
|
|
81
|
+
|
|
79
82
|
export { Alert, AlertTitle, AlertDescription } from './components/Alert/Alert';
|
|
80
83
|
export { Avatar, AvatarImage, AvatarFallback } from './components/Avatar/Avatar';
|
|
81
84
|
|
|
@@ -117,6 +120,14 @@ export {
|
|
|
117
120
|
SelectSeparator,
|
|
118
121
|
} from './components/Select';
|
|
119
122
|
|
|
123
|
+
// Tabs exports
|
|
124
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './components/Tabs/Tabs';
|
|
125
|
+
export type { TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps } from './components/Tabs/Tabs';
|
|
126
|
+
|
|
127
|
+
// Calendar exports
|
|
128
|
+
export { Calendar } from './components/Calendar/Calendar';
|
|
129
|
+
export type { CalendarProps } from './components/Calendar/Calendar';
|
|
130
|
+
|
|
120
131
|
// Modal functionality is provided by Dialog components
|
|
121
132
|
|
|
122
133
|
export {
|
package/src/rbac/hooks/index.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
export { useRBAC } from './useRBAC';
|
|
11
11
|
export { useResolvedScope } from './useResolvedScope';
|
|
12
12
|
export type { UseResolvedScopeOptions, UseResolvedScopeReturn } from './useResolvedScope';
|
|
13
|
+
export { useResourcePermissions } from './useResourcePermissions';
|
|
14
|
+
export type { UseResourcePermissionsOptions, ResourcePermissions } from './useResourcePermissions';
|
|
13
15
|
|
|
14
16
|
// Export all permission hooks
|
|
15
17
|
export {
|