@ceed/ads 1.25.1-next.3 → 1.27.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.
Files changed (62) hide show
  1. package/dist/chunks/rehype-accent-FZRUD7VI.js +39 -0
  2. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  3. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  4. package/dist/components/DataTable/components.d.ts +2 -1
  5. package/dist/components/DataTable/hooks.d.ts +1 -1
  6. package/dist/components/DataTable/styled.d.ts +3 -1
  7. package/dist/components/DataTable/types.d.ts +11 -0
  8. package/dist/components/DataTable/utils.d.ts +2 -2
  9. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  10. package/dist/components/data-display/Badge.md +71 -39
  11. package/dist/components/data-display/DataTable.md +177 -1
  12. package/dist/components/data-display/InfoSign.md +74 -98
  13. package/dist/components/data-display/Typography.md +363 -97
  14. package/dist/components/feedback/CircularProgress.md +257 -0
  15. package/dist/components/feedback/Dialog.md +76 -62
  16. package/dist/components/feedback/Modal.md +259 -44
  17. package/dist/components/feedback/Skeleton.md +280 -0
  18. package/dist/components/feedback/llms.txt +2 -0
  19. package/dist/components/inputs/Autocomplete.md +356 -107
  20. package/dist/components/inputs/ButtonGroup.md +115 -106
  21. package/dist/components/inputs/Calendar.md +98 -459
  22. package/dist/components/inputs/CurrencyInput.md +183 -5
  23. package/dist/components/inputs/DatePicker.md +108 -431
  24. package/dist/components/inputs/DateRangePicker.md +131 -492
  25. package/dist/components/inputs/FilterMenu.md +169 -19
  26. package/dist/components/inputs/FilterableCheckboxGroup.md +123 -23
  27. package/dist/components/inputs/FormControl.md +361 -0
  28. package/dist/components/inputs/IconButton.md +137 -88
  29. package/dist/components/inputs/Input.md +5 -0
  30. package/dist/components/inputs/MonthPicker.md +95 -422
  31. package/dist/components/inputs/MonthRangePicker.md +89 -466
  32. package/dist/components/inputs/PercentageInput.md +185 -16
  33. package/dist/components/inputs/RadioButton.md +163 -35
  34. package/dist/components/inputs/RadioList.md +241 -0
  35. package/dist/components/inputs/RadioTileGroup.md +150 -61
  36. package/dist/components/inputs/Select.md +222 -326
  37. package/dist/components/inputs/Slider.md +334 -0
  38. package/dist/components/inputs/Switch.md +136 -376
  39. package/dist/components/inputs/Textarea.md +213 -10
  40. package/dist/components/inputs/Uploader/Uploader.md +145 -66
  41. package/dist/components/inputs/llms.txt +3 -0
  42. package/dist/components/navigation/Breadcrumbs.md +80 -322
  43. package/dist/components/navigation/Dropdown.md +92 -221
  44. package/dist/components/navigation/IconMenuButton.md +40 -502
  45. package/dist/components/navigation/InsetDrawer.md +68 -738
  46. package/dist/components/navigation/Link.md +39 -298
  47. package/dist/components/navigation/Menu.md +92 -285
  48. package/dist/components/navigation/MenuButton.md +55 -448
  49. package/dist/components/navigation/Pagination.md +47 -338
  50. package/dist/components/navigation/ProfileMenu.md +45 -268
  51. package/dist/components/navigation/Stepper.md +160 -28
  52. package/dist/components/navigation/Tabs.md +57 -316
  53. package/dist/components/surfaces/Sheet.md +151 -334
  54. package/dist/guides/ThemeProvider.md +116 -0
  55. package/dist/guides/llms.txt +9 -0
  56. package/dist/index.browser.js +16 -18
  57. package/dist/index.browser.js.map +4 -4
  58. package/dist/index.cjs +381 -244
  59. package/dist/index.js +459 -378
  60. package/dist/llms.txt +8 -0
  61. package/framer/index.js +1 -166
  62. package/package.json +15 -16
@@ -0,0 +1,39 @@
1
+ // src/libs/rehype-accent/index.ts
2
+ import { visit } from "unist-util-visit";
3
+ function rehypeAccent(options) {
4
+ const { accentColor } = options;
5
+ return (tree) => {
6
+ visit(tree, "text", (node, index, parent) => {
7
+ const value = node.value;
8
+ const regex = /\|\|.*?\|\|/g;
9
+ let match;
10
+ let lastIndex = 0;
11
+ const newNodes = [];
12
+ while ((match = regex.exec(value)) !== null) {
13
+ if (match.index > lastIndex) {
14
+ newNodes.push({
15
+ type: "text",
16
+ value: value.slice(lastIndex, match.index)
17
+ });
18
+ }
19
+ const innerText = match[0].split("||")[1];
20
+ newNodes.push({
21
+ type: "element",
22
+ tagName: "span",
23
+ properties: { textColor: accentColor },
24
+ children: [{ type: "text", value: innerText }]
25
+ });
26
+ lastIndex = match.index + match[0].length;
27
+ }
28
+ if (lastIndex < value.length) {
29
+ newNodes.push({ type: "text", value: value.slice(lastIndex) });
30
+ }
31
+ if (newNodes.length) {
32
+ parent.children.splice(index, 1, ...newNodes);
33
+ }
34
+ });
35
+ };
36
+ }
37
+ export {
38
+ rehypeAccent
39
+ };
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { InputProps } from '@mui/joy';
3
3
  import { MotionProps } from 'framer-motion';
4
4
  export interface CurrencyInputProps {
5
- currency?: 'USD' | 'KRW';
5
+ currency?: 'USD' | 'KRW' | 'CAD';
6
6
  max?: number;
7
7
  value?: number;
8
8
  defaultValue?: number;
@@ -1,10 +1,10 @@
1
1
  export declare const useCurrencySetting: (props: {
2
- currency?: "USD" | "KRW" | "BHD" | undefined;
2
+ currency?: "USD" | "KRW" | "BHD" | "CAD" | undefined;
3
3
  placeholder?: string | undefined;
4
4
  }) => {
5
5
  symbol: string;
6
6
  thousandSeparator: string;
7
- decimalSeparator: string;
7
+ decimalSeparator: string | undefined;
8
8
  placeholder: string;
9
9
  fixedDecimalScale: boolean;
10
10
  decimalScale: number;
@@ -1,7 +1,8 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import type { ObjectLike, ColumnDef, Sort } from './types';
3
- export declare const TextEllipsis: ({ children }: {
3
+ export declare const TextEllipsis: ({ children, lineClamp }: {
4
4
  children: ReactNode;
5
+ lineClamp?: number | undefined;
5
6
  }) => React.JSX.Element;
6
7
  export declare const CellTextEllipsis: ({ children }: {
7
8
  children: ReactNode;
@@ -5,7 +5,7 @@ export declare function useColumnWidths<T>(columnsByField: {
5
5
  headerRef: React.RefObject<HTMLTableCellElement>;
6
6
  };
7
7
  }): Record<string, number>;
8
- export declare function useDataTableRenderer<T extends Record<PropertyKey, unknown>, GetId extends ((row: T) => any) | undefined = undefined>({ rows: _rows, columns: columnsProp, pinnedColumns, rowCount: totalRowsProp, initialState, pagination, paginationMode, paginationModel, onPaginationModelChange, sortModel: controlledSortModel, sortOrder: _sortOrder, selectionModel, onSortModelChange, onSelectionModelChange, editMode, getId: _getId, isTotalSelected: _isTotalSelected, isRowSelectable, columnGroupingModel, }: DataTableProps<T, GetId>): {
8
+ export declare function useDataTableRenderer<T extends Record<PropertyKey, unknown>, GetId extends ((row: T) => any) | undefined = undefined>({ rows: _rows, columns: columnsProp, pinnedColumns, rowCount: totalRowsProp, initialState, pagination, paginationMode, paginationModel, onPaginationModelChange, sortModel: controlledSortModel, sortOrder: _sortOrder, selectionModel, onSortModelChange, onSelectionModelChange, editMode, getId: _getId, isTotalSelected: _isTotalSelected, isRowSelectable, columnGroupingModel, columnVisibilityModel, onColumnVisibilityModelChange, }: DataTableProps<T, GetId>): {
9
9
  rowCount: number;
10
10
  selectableRowCount: number;
11
11
  page: number;
@@ -1,5 +1,7 @@
1
1
  import React, { RefObject } from 'react';
2
- export declare const EllipsisDiv: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof React.ClassAttributes<HTMLDivElement> | keyof React.HTMLAttributes<HTMLDivElement>>, {}>;
2
+ export declare const EllipsisDiv: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme> & {
3
+ lineClamp?: number | undefined;
4
+ }, React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
3
5
  export declare const OverlayWrapper: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLTableRowElement>, HTMLTableRowElement>, keyof React.ClassAttributes<HTMLTableRowElement> | keyof React.HTMLAttributes<HTMLTableRowElement>>, {}>;
4
6
  export declare const VirtualizedTableBody: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLTableSectionElement>, HTMLTableSectionElement>, keyof React.ClassAttributes<HTMLTableSectionElement> | keyof React.HTMLAttributes<HTMLTableSectionElement>>, {}>;
5
7
  export declare const StyledTableRow: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme> & {
@@ -103,6 +103,7 @@ export type BaseColumnDef<T extends Record<PropertyKey, V>, V, ID> = {
103
103
  description?: string;
104
104
  cellClassName?: TableCellClassNamePropType<T, T[K]>;
105
105
  headerClassName?: TableColumnHeaderClassNamePropType<T, T[K]>;
106
+ headerLineClamp?: 1 | 2;
106
107
  };
107
108
  }[keyof T];
108
109
  export type AutocompleteColumnDef<T extends Record<PropertyKey, string>, ID> = BaseColumnDef<T, string, ID> & {
@@ -147,6 +148,7 @@ export type ActionsColumnDef<T extends Record<PropertyKey, string>, ID> = BaseCo
147
148
  }) => ReactNode[];
148
149
  };
149
150
  export type ColumnDef<T extends Record<PropertyKey, any>, ID = unknown> = AutocompleteColumnDef<T, ID> | CurrencyColumnDef<T, ID> | DateColumnDef<T, ID> | NumberColumnDef<T, ID> | TextColumnDef<T, ID> | LongTextColumnDef<T, ID> | LinkColumnDef<T, ID> | SelectColumnDef<T, ID> | ActionsColumnDef<T, ID>;
151
+ export type ColumnVisibilityModel = Record<string, boolean>;
150
152
  export type Sort = 'asc' | 'desc' | null;
151
153
  export type SortModel<T extends Record<PropertyKey, any>> = {
152
154
  [K in keyof T]: {
@@ -164,6 +166,12 @@ export type DataTableProps<T extends Record<PropertyKey, any>, GetId extends ((r
164
166
  columns: ColumnDef<T, InferredIdType<T, GetId>>[];
165
167
  pinnedColumns?: PinnedColumns;
166
168
  columnGroupingModel?: ColumnGroupingModel<T, InferredIdType<T, GetId>>;
169
+ /**
170
+ * 컬럼의 표시/숨김 상태를 제어한다. field를 key로, boolean을 value로 사용한다.
171
+ * false로 설정된 컬럼만 숨겨지며, 모델에 포함되지 않은 컬럼은 기본적으로 표시된다.
172
+ */
173
+ columnVisibilityModel?: ColumnVisibilityModel;
174
+ onColumnVisibilityModelChange?: (model: ColumnVisibilityModel) => void;
167
175
  editMode?: boolean;
168
176
  /**
169
177
  * 체크박스가 있는 경우, 체크박스를 클릭했을 때 선택된 row의 index를 지정한다.
@@ -187,6 +195,9 @@ export type DataTableProps<T extends Record<PropertyKey, any>, GetId extends ((r
187
195
  sorting: Partial<{
188
196
  sortModel: SortModel<T>[];
189
197
  }>;
198
+ columns: Partial<{
199
+ columnVisibilityModel: ColumnVisibilityModel;
200
+ }>;
190
201
  }>;
191
202
  pagination?: boolean;
192
203
  paginationMode?: 'client' | 'server';
@@ -3,8 +3,8 @@ export declare function extractFieldsFromGroupingModel<T extends Record<Property
3
3
  export declare function reorderColumnsByGroupingModel<T extends Record<PropertyKey, any>, ID>(columns: ColumnDef<T, ID>[], columnGroupingModel: ColumnGroupingModel<T, ID>): ColumnDef<T, ID>[];
4
4
  export declare function flattenColumnGroups<T extends Record<PropertyKey, any>, ID>(items: ColumnGroupingModel<T, ID> | ColumnDef<T, ID>[], groupPath?: string[], columnIndex?: {
5
5
  current: number;
6
- }): FlattenedColumn<T, ID>[];
7
- export declare function calculateColumnGroups<T extends Record<PropertyKey, any>, ID>(columnGroupingModel: ColumnGroupingModel<T, ID>, columns: ColumnDef<T, ID>[]): {
6
+ }, visibleFields?: Set<string>): FlattenedColumn<T, ID>[];
7
+ export declare function calculateColumnGroups<T extends Record<PropertyKey, any>, ID>(columnGroupingModel: ColumnGroupingModel<T, ID>, columns: ColumnDef<T, ID>[], visibleFields?: Set<string>): {
8
8
  groups: ProcessedColumnGroup[][];
9
9
  maxLevel: number;
10
10
  fieldsInGroupingModel: Set<keyof T>;
@@ -26,7 +26,7 @@ interface BaseProfileMenuProps {
26
26
  src: string;
27
27
  alt: string;
28
28
  };
29
- caption?: React.ReactNode;
29
+ caption?: string;
30
30
  chip?: string;
31
31
  };
32
32
  menuItems: ({
@@ -2,7 +2,9 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
- Badge 컴포넌트는 다른 UI 요소 위에 작은 카운트, 상태, 또는 레이블을 표시하는 사용됩니다. 주로 알림 수, 상태 표시기, 또는 새로운 콘텐츠를 나타내는 활용됩니다.
5
+ Badge is a small visual indicator that overlays another UI element to display a count, status, or label. It is commonly used to show unread notification counts, online/offline status indicators, and "NEW" labels on menu items or navigation elements. The Badge wraps a child component (such as an icon button or avatar) and positions itself in the top-right corner by default.
6
+
7
+ Badge supports multiple colors, variants, and sizes for flexible visual customization. It also provides utility features like a `max` prop for truncating large numbers (e.g., "99+"), a `showZero` prop for controlling zero-count visibility, and a dot-only mode when no `badgeContent` is provided.
6
8
 
7
9
  ```tsx
8
10
  <Badge {...args}>
@@ -26,7 +28,7 @@ Badge 컴포넌트는 다른 UI 요소 위에 작은 카운트, 상태, 또는
26
28
 
27
29
  ```tsx
28
30
  import { Badge, IconButton } from '@ceed/ads';
29
- import { NotificationsIcon } from '@mui/icons-material';
31
+ import NotificationsIcon from '@mui/icons-material/Notifications';
30
32
 
31
33
  function MyComponent() {
32
34
  return (
@@ -39,11 +41,9 @@ function MyComponent() {
39
41
  }
40
42
  ```
41
43
 
42
- ## Examples
43
-
44
- ### Basic Usage
44
+ ## Basic
45
45
 
46
- 기본적인 Badge 사용법입니다.
46
+ Basic Badge usage showing numeric counts, max overflow, and text content.
47
47
 
48
48
  ```tsx
49
49
  <div style={{
@@ -70,9 +70,9 @@ function MyComponent() {
70
70
  </div>
71
71
  ```
72
72
 
73
- ### Colors
73
+ ## Colors
74
74
 
75
- 다양한 색상을 적용할 있습니다.
75
+ Apply semantic colors to convey the badge's meaning: `primary` for general counts, `danger` for urgent notifications, `success` for online status, and `warning` for attention items.
76
76
 
77
77
  ```tsx
78
78
  <div style={{
@@ -111,9 +111,9 @@ function MyComponent() {
111
111
  </div>
112
112
  ```
113
113
 
114
- ### Variants
114
+ ## Variants
115
115
 
116
- 다양한 스타일 변형을 제공합니다.
116
+ Four style variants are available: `solid` (filled), `soft` (subtle background), `outlined` (border only), and `plain` (minimal).
117
117
 
118
118
  ```tsx
119
119
  <div style={{
@@ -146,9 +146,9 @@ function MyComponent() {
146
146
  </div>
147
147
  ```
148
148
 
149
- ### Sizes
149
+ ## Sizes
150
150
 
151
- 크기를 조절할 있습니다.
151
+ Three sizes -- `sm`, `md`, and `lg` -- allow the badge to scale proportionally with its wrapped element.
152
152
 
153
153
  ```tsx
154
154
  <div style={{
@@ -176,9 +176,9 @@ function MyComponent() {
176
176
  </div>
177
177
  ```
178
178
 
179
- ### With Avatar
179
+ ## With Avatar
180
180
 
181
- 아바타와 함께 사용할 있습니다.
181
+ Badge pairs naturally with Avatar to indicate user status (e.g., notification count or online indicator).
182
182
 
183
183
  ```tsx
184
184
  <div style={{
@@ -195,9 +195,9 @@ function MyComponent() {
195
195
  </div>
196
196
  ```
197
197
 
198
- ### Dot Badge
198
+ ## Dot Badge
199
199
 
200
- 내용 없이 점으로만 표시할 있습니다.
200
+ When no `badgeContent` is provided, Badge renders as a small dot. This is useful for simple presence or status indicators without a specific count.
201
201
 
202
202
  ```tsx
203
203
  <div style={{
@@ -216,9 +216,9 @@ function MyComponent() {
216
216
  </div>
217
217
  ```
218
218
 
219
- ### Max Count
219
+ ## Max Count
220
220
 
221
- 최대 카운트를 설정하여 숫자가 넘을 '+' 표시를 있습니다.
221
+ Use the `max` prop to cap the displayed number. When `badgeContent` exceeds `max`, it renders as "max+" (e.g., "99+").
222
222
 
223
223
  ```tsx
224
224
  <div style={{
@@ -245,9 +245,9 @@ function MyComponent() {
245
245
  </div>
246
246
  ```
247
247
 
248
- ### Show Zero
248
+ ## Show Zero
249
249
 
250
- 기본적으로 0 때는 숨겨지지만, showZero 옵션으로 표시할 있습니다.
250
+ By default, Badge hides when `badgeContent` is `0`. Use the `showZero` prop to force it to remain visible.
251
251
 
252
252
  ```tsx
253
253
  <div style={{
@@ -276,40 +276,72 @@ function MyComponent() {
276
276
 
277
277
  ## Common Use Cases
278
278
 
279
- ### Notification Count
279
+ ### Notification Bell
280
280
 
281
281
  ```tsx
282
- <Badge badgeContent={unreadCount} color="danger">
283
- <IconButton>
284
- <NotificationsIcon />
285
- </IconButton>
286
- </Badge>
282
+ import { Badge, IconButton } from '@ceed/ads';
283
+ import NotificationsIcon from '@mui/icons-material/Notifications';
284
+
285
+ function NotificationBell({ unreadCount }) {
286
+ return (
287
+ <Badge badgeContent={unreadCount} max={99} color="danger">
288
+ <IconButton aria-label={`${unreadCount} unread notifications`}>
289
+ <NotificationsIcon />
290
+ </IconButton>
291
+ </Badge>
292
+ );
293
+ }
287
294
  ```
288
295
 
289
- ### Status Indicator
296
+ ### Online Status on Avatar
290
297
 
291
298
  ```tsx
292
- <Badge color="success" variant="solid">
293
- <Avatar src="/user-avatar.jpg" />
294
- </Badge>
299
+ import { Badge, Avatar } from '@ceed/ads';
300
+
301
+ function UserAvatar({ name, avatarUrl, isOnline }) {
302
+ return (
303
+ <Badge
304
+ color={isOnline ? 'success' : 'neutral'}
305
+ variant="solid"
306
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
307
+ >
308
+ <Avatar src={avatarUrl} alt={name} />
309
+ </Badge>
310
+ );
311
+ }
295
312
  ```
296
313
 
297
- ### New Items
314
+ ### New Feature Label
298
315
 
299
316
  ```tsx
300
- <Badge badgeContent="NEW" color="warning">
301
- <Button>메뉴</Button>
302
- </Badge>
317
+ import { Badge, Button } from '@ceed/ads';
318
+
319
+ function FeatureButton() {
320
+ return (
321
+ <Badge badgeContent="NEW" color="warning" size="sm">
322
+ <Button variant="outlined">Analytics</Button>
323
+ </Badge>
324
+ );
325
+ }
303
326
  ```
304
327
 
305
328
  ## Best Practices
306
329
 
307
- 1. **의미 있는 색상**: 색상은 의미를 가지도록 사용하세요 (빨간색은 중요한 알림, 초록색은 성공 상태 등).
330
+ - **Use semantic colors consistently.** Reserve `danger` for urgent notifications, `success` for positive status (e.g., online), `warning` for attention-needed items, and `primary` for neutral counts.
331
+ - ✔ `color="danger"` for unread message count
332
+ - ✘ `color="primary"` for critical alert count
333
+
334
+ - **Set a `max` value for numeric badges.** Avoid displaying very large numbers like "1,247" in a tiny badge. Use `max={99}` or `max={999}` to keep the badge compact and readable.
335
+
336
+ - **Keep badge content short.** Badges work best with numbers (1-3 digits) or very short text ("NEW", "HOT"). Long text will overflow and look broken.
308
337
 
309
- 2. **적절한 콘텐츠**: 너무 텍스트는 피하고, 숫자나 짧은 텍스트를 사용하세요.
338
+ - **Use dot badges for binary states.** When the specific count does not matter (e.g., "has unread items" vs. "12 unread items"), use a dot badge by omitting `badgeContent`.
310
339
 
311
- 3. **대비**: Badge의 색상이 배경과 충분한 대비를 가지도록 하세요.
340
+ - **Do not badge everything.** Only add badges to elements where the count or status information is genuinely useful. Overuse diminishes their attention-grabbing effect.
312
341
 
313
- 4. **접근성**: 시각적 정보만으로 의존하지 않고, 스크린 리더를 위한 적절한 레이블을 제공하세요.
342
+ ## Accessibility
314
343
 
315
- 5. **일관성**: 애플리케이션 전체에서 Badge 사용 패턴을 일관되게 유지하세요.
344
+ - Badge automatically applies `aria-label` or equivalent attributes so screen readers can announce the badge content alongside the wrapped element.
345
+ - When using Badge on an IconButton, always provide an `aria-label` on the IconButton that includes the badge context (e.g., `aria-label="5 unread notifications"`).
346
+ - Avoid relying solely on color to convey badge meaning. Pair colored badges with icons, text, or positional context so color-blind users can also interpret the status.
347
+ - Dot badges (without `badgeContent`) should have an accessible label on the parent element that describes the status (e.g., "User is online").
@@ -865,6 +865,106 @@ const columnGroupingModel = [
865
865
  <DataTable rows={rows} columns={columns} columnGroupingModel={columnGroupingModel} />;
866
866
  ```
867
867
 
868
+ ### Column Visibility
869
+
870
+ You can control which columns are visible using `columnVisibilityModel`.
871
+ Columns set to `false` are hidden. Columns not in the model are visible by default.
872
+ Hidden columns are completely removed from the DOM for optimal rendering performance.
873
+
874
+ ```tsx
875
+ <Stack spacing={2}>
876
+ <Stack direction="row" spacing={1} sx={{
877
+ flexWrap: 'wrap'
878
+ }}>
879
+ {allColumns.map(col => <label key={col.field as string} style={{
880
+ display: 'flex',
881
+ alignItems: 'center',
882
+ gap: 4
883
+ }}>
884
+ <input type="checkbox" checked={visibilityModel[col.field as string] !== false} onChange={e => setVisibilityModel(prev => ({
885
+ ...prev,
886
+ [col.field as string]: e.target.checked
887
+ }))} />
888
+ {col.headerName}
889
+ </label>)}
890
+ </Stack>
891
+ <DataTable rows={rows2} columns={allColumns} columnVisibilityModel={visibilityModel} onColumnVisibilityModelChange={setVisibilityModel} noWrap />
892
+ </Stack>
893
+ ```
894
+
895
+ #### Controlled Mode
896
+
897
+ ```tsx
898
+ const [visibilityModel, setVisibilityModel] = useState({ fat: false });
899
+
900
+ <DataTable
901
+ rows={rows}
902
+ columns={columns}
903
+ columnVisibilityModel={visibilityModel}
904
+ onColumnVisibilityModelChange={setVisibilityModel}
905
+ />
906
+ ```
907
+
908
+ #### Uncontrolled Mode (initialState)
909
+
910
+ ```tsx
911
+ <DataTable
912
+ rows={rows}
913
+ columns={columns}
914
+ initialState={{
915
+ columns: { columnVisibilityModel: { fat: false, carbs: false } },
916
+ }}
917
+ />
918
+ ```
919
+
920
+ #### Column Visibility with Column Groups
921
+
922
+ ```tsx
923
+ <Stack spacing={2}>
924
+ <Stack direction="row" spacing={1} sx={{
925
+ flexWrap: 'wrap'
926
+ }}>
927
+ {columns.map(col => <label key={col.field as string} style={{
928
+ display: 'flex',
929
+ alignItems: 'center',
930
+ gap: 4
931
+ }}>
932
+ <input type="checkbox" checked={visibilityModel[col.field as string] !== false} onChange={e => setVisibilityModel(prev => ({
933
+ ...prev,
934
+ [col.field as string]: e.target.checked
935
+ }))} />
936
+ {col.headerName}
937
+ </label>)}
938
+ </Stack>
939
+ <DataTable rows={rows4} columns={columns} columnGroupingModel={[{
940
+ groupId: 'group1',
941
+ headerName: 'Group 1',
942
+ children: [{
943
+ field: 'id'
944
+ }, {
945
+ field: 'number'
946
+ }, {
947
+ field: 'string'
948
+ }]
949
+ }, {
950
+ groupId: 'group2',
951
+ headerName: 'Group 2',
952
+ children: [{
953
+ field: 'date'
954
+ }, {
955
+ field: 'object'
956
+ }]
957
+ }]} columnVisibilityModel={visibilityModel} onColumnVisibilityModelChange={setVisibilityModel} noWrap />
958
+ </Stack>
959
+ ```
960
+
961
+ #### Notes
962
+
963
+ - Hidden columns are completely removed from the DOM for optimal performance.
964
+ - Sorting on hidden columns is preserved — row order is maintained even if the sort column is hidden.
965
+ - Works with column grouping: hidden columns reduce group colspan, and fully hidden groups disappear.
966
+ - Works with pinned columns: hidden pinned columns are excluded from sticky position calculations.
967
+
868
968
  ### Column Resizing
869
969
 
870
970
  Setting `resizable: true` on a column allows you to adjust the column width by dragging.
@@ -895,6 +995,82 @@ const columns = [
895
995
  ];
896
996
  ```
897
997
 
998
+ ### Two-Line Headers (headerLineClamp)
999
+
1000
+ Setting `headerLineClamp: 2` on a column allows the header text to wrap up to 2 lines. Text exceeding 2 lines is truncated with an ellipsis, and a tooltip is displayed on hover when truncation occurs.
1001
+
1002
+ ```tsx
1003
+ <Box sx={{
1004
+ width: 500
1005
+ }}>
1006
+ <DataTable rows={manyRows} columns={[{
1007
+ field: 'dessert',
1008
+ headerName: 'Dessert Name (100g serving)',
1009
+ headerLineClamp: 2,
1010
+ width: '120px'
1011
+ }, {
1012
+ field: 'calories',
1013
+ headerName: 'Calories (kcal)',
1014
+ headerLineClamp: 2,
1015
+ type: 'number',
1016
+ width: '80px'
1017
+ }, {
1018
+ field: 'fat',
1019
+ headerName: 'Fat Content (grams)',
1020
+ headerLineClamp: 2,
1021
+ type: 'number',
1022
+ width: '80px'
1023
+ }, {
1024
+ field: 'carbs',
1025
+ headerName: 'Total Carbohydrates (g)',
1026
+ headerLineClamp: 2,
1027
+ type: 'number',
1028
+ width: '90px'
1029
+ }, {
1030
+ field: 'protein',
1031
+ headerName: 'Protein Amount (g)',
1032
+ type: 'number',
1033
+ width: '120px'
1034
+ }]} stickyHeader slotProps={{
1035
+ background: {
1036
+ style: {
1037
+ height: '400px'
1038
+ }
1039
+ }
1040
+ }} />
1041
+ </Box>
1042
+ ```
1043
+
1044
+ ```tsx
1045
+ const columns = [
1046
+ {
1047
+ field: 'calories',
1048
+ headerName: 'Calories (kcal)',
1049
+ headerLineClamp: 2,
1050
+ type: 'number',
1051
+ width: '80px',
1052
+ },
1053
+ {
1054
+ field: 'fat',
1055
+ headerName: 'Fat Content (grams)',
1056
+ headerLineClamp: 2,
1057
+ type: 'number',
1058
+ width: '80px',
1059
+ },
1060
+ {
1061
+ field: 'protein',
1062
+ headerName: 'Protein (g)', // no headerLineClamp — single line (default)
1063
+ type: 'number',
1064
+ },
1065
+ ];
1066
+ ```
1067
+
1068
+ #### Notes
1069
+
1070
+ - `headerLineClamp` is opt-in per column. Columns without it remain single-line.
1071
+ - Supported values: `1 | 2`.
1072
+ - Works with all existing features: sorting, pinning, resizing, and column grouping.
1073
+
898
1074
  ## Editing Features
899
1075
 
900
1076
  ### Inline Editing
@@ -1038,7 +1214,7 @@ You can use `onCellEditStart` and `onCellEditStop` to detect when editing starts
1038
1214
  </Typography>
1039
1215
  {editLog.length === 0 ? <Typography level="body-xs" textColor="text.secondary">
1040
1216
  No edits yet. Click on a cell to edit.
1041
- </Typography> : editLog.map((log, i) => <Typography key={i} level="body-xs">
1217
+ </Typography> : editLog.map(log => <Typography key={log} level="body-xs">
1042
1218
  {log}
1043
1219
  </Typography>)}
1044
1220
  </Box>