@ceed/cds 1.21.0 → 1.22.0

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.
@@ -0,0 +1,56 @@
1
+ import React, { ReactNode } from 'react';
2
+ import type { SxProps } from '@mui/joy/styles/types';
3
+ export interface RadioTileOption<T = string> {
4
+ value: T;
5
+ label: ReactNode;
6
+ disabled?: boolean;
7
+ startDecorator?: ReactNode;
8
+ }
9
+ export interface RadioTileGroupProps<T = string> {
10
+ /**
11
+ * @default 'sm'
12
+ */
13
+ size?: 'sm' | 'md' | 'lg';
14
+ options: RadioTileOption<T>[];
15
+ value?: T;
16
+ defaultValue?: T;
17
+ name?: string;
18
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
19
+ sx?: SxProps;
20
+ disabled?: boolean;
21
+ className?: string;
22
+ useIndicator?: boolean;
23
+ flex?: boolean;
24
+ /**
25
+ * 지정하지 않으면 한 row에 모든 옵션이 렌더링된다.
26
+ * - 특정 rows 이내로 렌더링 하고 싶으면 `columns: Math.ceil(options.length / 원하는 rows)`로 설정할 수 있다.
27
+ * - 수직으로 렌더링 하고 싶으면 `columns: 1`로 설정할 수 있다.
28
+ * - `flex` 옵션과 함께 사용할수 있다.
29
+ */
30
+ columns?: number;
31
+ /**
32
+ * @default 'center'
33
+ */
34
+ textAlign?: 'start' | 'center';
35
+ /**
36
+ * Label for the RadioTileGroup
37
+ */
38
+ label?: React.ReactNode;
39
+ /**
40
+ * Helper text for the RadioTileGroup
41
+ */
42
+ helperText?: React.ReactNode;
43
+ /**
44
+ * Whether the RadioTileGroup has an error
45
+ */
46
+ error?: boolean;
47
+ /**
48
+ * Whether the RadioTileGroup is required
49
+ */
50
+ required?: boolean;
51
+ }
52
+ declare function RadioTileGroup<T extends string | number = string>(props: RadioTileGroupProps<T>): React.JSX.Element;
53
+ declare namespace RadioTileGroup {
54
+ var displayName: string;
55
+ }
56
+ export { RadioTileGroup };
@@ -0,0 +1,3 @@
1
+ import { RadioTileGroup } from './RadioTileGroup';
2
+ export * from './RadioTileGroup';
3
+ export default RadioTileGroup;
@@ -43,9 +43,7 @@ function ConfirmationDialog({ open, onClose, onConfirm }) {
43
43
  <Button variant="plain" color="neutral" onClick={onClose}>
44
44
  Cancel
45
45
  </Button>
46
- <Button onClick={onConfirm}>
47
- Confirm
48
- </Button>
46
+ <Button onClick={onConfirm}>Confirm</Button>
49
47
  </>
50
48
  }
51
49
  >
@@ -94,6 +92,57 @@ Dialog Content
94
92
  </DialogFrame>
95
93
  ```
96
94
 
95
+ ### Standalone Usage
96
+
97
+ DialogFrame can be used without a Modal wrapper for embedding dialog-style layouts directly within a page.
98
+
99
+ > ⚠️ **Important** ⚠️
100
+ >
101
+ > When using DialogFrame without Modal, the parent container **must** provide explicit `width` and `height` values.
102
+ > DialogFrame inherits its dimensions from `ModalDialog`, which normally receives sizing from the Modal overlay.
103
+ > Without these constraints, the component will not render with correct dimensions.
104
+
105
+ ```tsx
106
+ <Box sx={{
107
+ width: 480,
108
+ height: 300
109
+ }}>
110
+ <DialogFrame {...args} title="Standalone Dialog" actions={<>
111
+ <Button variant="plain" color="neutral">
112
+ Cancel
113
+ </Button>
114
+ <Button variant="plain">Confirm</Button>
115
+ </>}>
116
+ DialogFrame used without Modal. The parent container must provide explicit width and height.
117
+ </DialogFrame>
118
+ </Box>
119
+ ```
120
+
121
+ ```tsx
122
+ import { DialogFrame, Button, Box } from '@ceed/cds';
123
+
124
+ // Standalone usage requires explicit container dimensions
125
+ function EmbeddedDialog() {
126
+ return (
127
+ <Box sx={{ width: 480, height: 300 }}>
128
+ <DialogFrame
129
+ title="Settings"
130
+ actions={
131
+ <>
132
+ <Button variant="plain" color="neutral">
133
+ Cancel
134
+ </Button>
135
+ <Button>Save</Button>
136
+ </>
137
+ }
138
+ >
139
+ This dialog is embedded directly in the page layout.
140
+ </DialogFrame>
141
+ </Box>
142
+ );
143
+ }
144
+ ```
145
+
97
146
  ## When to Use
98
147
 
99
148
  ### ✅ Good Use Cases
@@ -177,21 +226,11 @@ function QuickAddDialog({ open, onClose, onSubmit }) {
177
226
  <Stack gap={2}>
178
227
  <FormControl>
179
228
  <FormLabel>Name</FormLabel>
180
- <Input
181
- value={name}
182
- onChange={(e) => setName(e.target.value)}
183
- placeholder="Enter name"
184
- autoFocus
185
- />
229
+ <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter name" autoFocus />
186
230
  </FormControl>
187
231
  <FormControl>
188
232
  <FormLabel>Email</FormLabel>
189
- <Input
190
- type="email"
191
- value={email}
192
- onChange={(e) => setEmail(e.target.value)}
193
- placeholder="Enter email"
194
- />
233
+ <Input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter email" />
195
234
  </FormControl>
196
235
  </Stack>
197
236
  </DialogFrame>
@@ -216,15 +255,11 @@ function UnsavedChangesDialog({ open, onClose, onDiscard, onSave }) {
216
255
  <Button variant="outlined" color="danger" onClick={onDiscard}>
217
256
  Discard
218
257
  </Button>
219
- <Button onClick={onSave}>
220
- Save Changes
221
- </Button>
258
+ <Button onClick={onSave}>Save Changes</Button>
222
259
  </>
223
260
  }
224
261
  >
225
- <Typography>
226
- You have unsaved changes. Would you like to save them before leaving?
227
- </Typography>
262
+ <Typography>You have unsaved changes. Would you like to save them before leaving?</Typography>
228
263
  </DialogFrame>
229
264
  </Modal>
230
265
  );
@@ -237,14 +272,7 @@ function UnsavedChangesDialog({ open, onClose, onDiscard, onSave }) {
237
272
  function InfoDialog({ open, onClose, title, message }) {
238
273
  return (
239
274
  <Modal open={open} onClose={onClose}>
240
- <DialogFrame
241
- title={title}
242
- actions={
243
- <Button onClick={onClose}>
244
- Got it
245
- </Button>
246
- }
247
- >
275
+ <DialogFrame title={title} actions={<Button onClick={onClose}>Got it</Button>}>
248
276
  <Typography>{message}</Typography>
249
277
  </DialogFrame>
250
278
  </Modal>
@@ -301,18 +329,8 @@ function ProcessingDialog({ open, status, onClose }) {
301
329
  return (
302
330
  <Modal open={open} onClose={isProcessing ? undefined : onClose}>
303
331
  <DialogFrame
304
- title={
305
- isProcessing ? 'Processing...' :
306
- isSuccess ? 'Success!' :
307
- 'Error'
308
- }
309
- actions={
310
- !isProcessing && (
311
- <Button onClick={onClose}>
312
- {isSuccess ? 'Done' : 'Try Again'}
313
- </Button>
314
- )
315
- }
332
+ title={isProcessing ? 'Processing...' : isSuccess ? 'Success!' : 'Error'}
333
+ actions={!isProcessing && <Button onClick={onClose}>{isSuccess ? 'Done' : 'Try Again'}</Button>}
316
334
  >
317
335
  <Box sx={{ textAlign: 'center', py: 2 }}>
318
336
  {isProcessing && <CircularProgress />}
@@ -457,9 +475,7 @@ DialogFrame should be wrapped in Modal for proper behavior:
457
475
  // Fullscreen for complex content or mobile
458
476
  <Modal open={open} onClose={onClose}>
459
477
  <DialogFrame fullscreen title="Edit Profile">
460
- <Box sx={{ p: 2 }}>
461
- {/* Large form or content */}
462
- </Box>
478
+ <Box sx={{ p: 2 }}>{/* Large form or content */}</Box>
463
479
  </DialogFrame>
464
480
  </Modal>
465
481
  ```
@@ -500,9 +516,7 @@ DialogFrame inherits accessibility features from Modal:
500
516
 
501
517
  ```tsx
502
518
  // Title provides context
503
- <DialogFrame title="Confirm Deletion">
504
- {/* Content is read after title */}
505
- </DialogFrame>
519
+ <DialogFrame title="Confirm Deletion">{/* Content is read after title */}</DialogFrame>
506
520
  ```
507
521
 
508
522
  ## Best Practices
@@ -530,9 +544,7 @@ DialogFrame inherits accessibility features from Modal:
530
544
  ```tsx
531
545
  // ✅ Good: Brief, scannable content
532
546
  <DialogFrame title="Delete Project">
533
- <Typography>
534
- This will permanently delete the project and all its data.
535
- </Typography>
547
+ <Typography>This will permanently delete the project and all its data.</Typography>
536
548
  </DialogFrame>
537
549
  ```
538
550
 
@@ -560,7 +572,7 @@ DialogFrame inherits accessibility features from Modal:
560
572
  // ❌ Bad: Dialog for simple feedback
561
573
  <DialogFrame title="Success">
562
574
  <Typography>Item saved!</Typography>
563
- </DialogFrame>
575
+ </DialogFrame>;
564
576
 
565
577
  // ✅ Good: Use Toast
566
578
  showToast({ message: 'Item saved!' });
@@ -570,9 +582,7 @@ showToast({ message: 'Item saved!' });
570
582
 
571
583
  ```tsx
572
584
  // ❌ Bad: Complex form in dialog
573
- <DialogFrame title="Create Account">
574
- {/* 20+ form fields */}
575
- </DialogFrame>
585
+ <DialogFrame title="Create Account">{/* 20+ form fields */}</DialogFrame>
576
586
  ```
577
587
 
578
588
  3. **Don't use vague button labels**: Be specific about actions
@@ -599,9 +609,7 @@ For dialogs with heavy content:
599
609
  function HeavyDialog({ open, onClose }) {
600
610
  return (
601
611
  <Modal open={open} onClose={onClose}>
602
- <DialogFrame title="Data Preview">
603
- {open && <HeavyDataComponent />}
604
- </DialogFrame>
612
+ <DialogFrame title="Data Preview">{open && <HeavyDataComponent />}</DialogFrame>
605
613
  </Modal>
606
614
  );
607
615
  }
@@ -625,11 +633,13 @@ const handleCancel = useCallback(() => {
625
633
  Unmount dialog completely when not needed:
626
634
 
627
635
  ```tsx
628
- {open && (
629
- <Modal open={open} onClose={onClose}>
630
- <DialogFrame>...</DialogFrame>
631
- </Modal>
632
- )}
636
+ {
637
+ open && (
638
+ <Modal open={open} onClose={onClose}>
639
+ <DialogFrame>...</DialogFrame>
640
+ </Modal>
641
+ );
642
+ }
633
643
  ```
634
644
 
635
645
  DialogFrame provides a consistent structure for dialog content. Combine it with Modal for proper overlay behavior, keep content concise and actionable, and always provide clear options for users to proceed or cancel.
@@ -43,6 +43,7 @@ export { Navigator } from './Navigator';
43
43
  export { Pagination } from './Pagination';
44
44
  export { PercentageInput } from './PercentageInput';
45
45
  export { Radio, RadioGroup } from './Radio';
46
+ export { RadioTileGroup } from './RadioTileGroup';
46
47
  export { RadioList } from './RadioList';
47
48
  export { Select, Option } from './Select';
48
49
  export { Sheet } from './Sheet';