@liiift-studio/mac-os9-ui 0.2.21 → 0.2.24

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.
@@ -1,12 +1,57 @@
1
1
  import React from 'react';
2
2
  import { WindowProps } from '../Window/Window';
3
- import { ListColumn, ListItem } from '../ListView/ListView';
4
- export interface FolderListProps extends Omit<WindowProps, 'children'> {
3
+ import { WindowPosition } from '../../types';
4
+ import { ListColumn, ListItem, RowRenderState, RowDefaultProps, CellRenderState, HeaderCellRenderState, HeaderCellDefaultProps } from '../ListView/ListView';
5
+ /**
6
+ * Classes for targeting FolderList sub-elements
7
+ */
8
+ export interface FolderListClasses {
9
+ /** Root window container */
10
+ root?: string;
11
+ /** Window component */
12
+ window?: string;
13
+ /** Title bar */
14
+ titleBar?: string;
15
+ /** ListView container */
16
+ listView?: string;
17
+ /** ListView header */
18
+ header?: string;
19
+ /** ListView header cell */
20
+ headerCell?: string;
21
+ /** ListView body */
22
+ body?: string;
23
+ /** ListView row */
24
+ row?: string;
25
+ /** ListView cell */
26
+ cell?: string;
27
+ }
28
+ export interface FolderListProps extends Omit<WindowProps, 'children' | 'classes'> {
5
29
  /**
6
30
  * Column definitions for the list
7
31
  * @default [{ key: 'name', label: 'Name' }, { key: 'modified', label: 'Date Modified' }, { key: 'size', label: 'Size' }]
8
32
  */
9
33
  columns?: ListColumn[];
34
+ /**
35
+ * Whether the folder list window can be dragged by its title bar
36
+ * Window starts in normal flow and becomes absolutely positioned when dragged
37
+ * @default false
38
+ */
39
+ draggable?: boolean;
40
+ /**
41
+ * Initial position for draggable folder lists (uncontrolled)
42
+ * Only used when draggable is true
43
+ */
44
+ defaultPosition?: WindowPosition;
45
+ /**
46
+ * Controlled position for draggable folder lists
47
+ * Only used when draggable is true
48
+ */
49
+ position?: WindowPosition;
50
+ /**
51
+ * Callback when folder list position changes (during drag)
52
+ * Only called when draggable is true
53
+ */
54
+ onPositionChange?: (position: WindowPosition) => void;
10
55
  /**
11
56
  * Items to display in the list
12
57
  */
@@ -24,9 +69,13 @@ export interface FolderListProps extends Omit<WindowProps, 'children'> {
24
69
  */
25
70
  onItemOpen?: (item: ListItem) => void;
26
71
  /**
27
- * Callback when mouse enters an item
72
+ * Callback when mouse enters an item (row-level)
28
73
  */
29
74
  onItemMouseEnter?: (item: ListItem) => void;
75
+ /**
76
+ * Callback when mouse leaves an item (row-level)
77
+ */
78
+ onItemMouseLeave?: (item: ListItem) => void;
30
79
  /**
31
80
  * Callback when column header is clicked for sorting
32
81
  */
@@ -40,6 +89,47 @@ export interface FolderListProps extends Omit<WindowProps, 'children'> {
40
89
  * @default 400
41
90
  */
42
91
  listHeight?: number | string;
92
+ /**
93
+ * Custom classes for targeting sub-elements
94
+ */
95
+ classes?: FolderListClasses;
96
+ /**
97
+ * Override row rendering
98
+ * @param item - The list item
99
+ * @param state - Row state (selected, hovered, index)
100
+ * @param defaultProps - Props to spread on custom element for accessibility
101
+ * @returns Custom row element (fully replaces default)
102
+ */
103
+ renderRow?: (item: ListItem, state: RowRenderState, defaultProps: RowDefaultProps) => React.ReactNode;
104
+ /**
105
+ * Override cell rendering
106
+ * @param value - Cell value (item[columnKey])
107
+ * @param item - Full item object
108
+ * @param column - Column definition
109
+ * @param state - Cell state (hovered, selected row, indices)
110
+ * @returns Custom cell content (fully replaces default)
111
+ */
112
+ renderCell?: (value: any, item: ListItem, column: ListColumn, state: CellRenderState) => React.ReactNode;
113
+ /**
114
+ * Override header cell rendering
115
+ * @param column - Column definition
116
+ * @param state - Header state (sorted, direction)
117
+ * @param defaultProps - Props to spread on custom element
118
+ * @returns Custom header cell element (fully replaces default)
119
+ */
120
+ renderHeaderCell?: (column: ListColumn, state: HeaderCellRenderState, defaultProps: HeaderCellDefaultProps) => React.ReactNode;
121
+ /**
122
+ * Callback when a cell is clicked
123
+ */
124
+ onCellClick?: (item: ListItem, column: ListColumn, event: React.MouseEvent) => void;
125
+ /**
126
+ * Callback when mouse enters a cell
127
+ */
128
+ onCellMouseEnter?: (item: ListItem, column: ListColumn) => void;
129
+ /**
130
+ * Callback when mouse leaves a cell
131
+ */
132
+ onCellMouseLeave?: (item: ListItem, column: ListColumn) => void;
43
133
  }
44
134
  /**
45
135
  * Mac OS 9 style FolderList component
@@ -49,6 +139,7 @@ export interface FolderListProps extends Omit<WindowProps, 'children'> {
49
139
  *
50
140
  * @example
51
141
  * ```tsx
142
+ * // Basic folder list
52
143
  * <FolderList
53
144
  * title="My Documents"
54
145
  * items={[
@@ -58,8 +149,14 @@ export interface FolderListProps extends Omit<WindowProps, 'children'> {
58
149
  * selectedIds={['1']}
59
150
  * onSelectionChange={(ids) => console.log('Selected:', ids)}
60
151
  * onItemOpen={(item) => console.log('Open:', item.name)}
61
- * onItemMouseEnter={(item) => console.log('Hovering:', item.name)}
62
- * onMouseEnter={(e) => console.log('Mouse entered folder list')}
152
+ * />
153
+ *
154
+ * // Draggable folder list
155
+ * <FolderList
156
+ * title="My Documents"
157
+ * items={items}
158
+ * draggable
159
+ * defaultPosition={{ x: 100, y: 100 }}
63
160
  * />
64
161
  * ```
65
162
  */
@@ -1,2 +1,2 @@
1
1
  export { FolderList, FolderList as default } from './FolderList';
2
- export type { FolderListProps } from './FolderList';
2
+ export type { FolderListProps, FolderListClasses } from './FolderList';
@@ -33,6 +33,84 @@ export interface ListItem {
33
33
  */
34
34
  icon?: React.ReactNode;
35
35
  }
36
+ /**
37
+ * Classes for targeting ListView sub-elements
38
+ */
39
+ export interface ListViewClasses {
40
+ /** Root container */
41
+ root?: string;
42
+ /** Header row container */
43
+ header?: string;
44
+ /** Individual header cell */
45
+ headerCell?: string;
46
+ /** Body container (scrollable area) */
47
+ body?: string;
48
+ /** Individual row */
49
+ row?: string;
50
+ /** Individual cell */
51
+ cell?: string;
52
+ }
53
+ /**
54
+ * Row render prop state
55
+ */
56
+ export interface RowRenderState {
57
+ /** Whether this row is selected */
58
+ isSelected: boolean;
59
+ /** Whether this row is being hovered */
60
+ isHovered: boolean;
61
+ /** Row index in the list */
62
+ index: number;
63
+ }
64
+ /**
65
+ * Row render prop default props
66
+ * Spread these on your custom element for accessibility and behavior
67
+ */
68
+ export interface RowDefaultProps {
69
+ key: string;
70
+ className: string;
71
+ onClick: (e: React.MouseEvent) => void;
72
+ onDoubleClick: () => void;
73
+ onMouseEnter: () => void;
74
+ onMouseLeave: () => void;
75
+ 'data-selected': boolean;
76
+ 'data-index': number;
77
+ 'data-item-id': string;
78
+ }
79
+ /**
80
+ * Cell render prop state
81
+ */
82
+ export interface CellRenderState {
83
+ /** Whether this cell is being hovered */
84
+ isHovered: boolean;
85
+ /** Whether the row containing this cell is selected */
86
+ isRowSelected: boolean;
87
+ /** Column index */
88
+ columnIndex: number;
89
+ /** Row index */
90
+ rowIndex: number;
91
+ }
92
+ /**
93
+ * Header cell render prop state
94
+ */
95
+ export interface HeaderCellRenderState {
96
+ /** Whether this column is currently sorted */
97
+ isSorted: boolean;
98
+ /** Current sort direction if sorted */
99
+ sortDirection?: 'asc' | 'desc';
100
+ }
101
+ /**
102
+ * Header cell render prop default props
103
+ */
104
+ export interface HeaderCellDefaultProps {
105
+ key: string;
106
+ className: string;
107
+ style: React.CSSProperties;
108
+ onClick: () => void;
109
+ 'data-column': string;
110
+ 'data-sortable': boolean;
111
+ 'data-sorted'?: boolean;
112
+ 'data-sort-direction'?: 'asc' | 'desc';
113
+ }
36
114
  export interface ListViewProps {
37
115
  /**
38
116
  * Column definitions
@@ -55,9 +133,13 @@ export interface ListViewProps {
55
133
  */
56
134
  onItemOpen?: (item: ListItem) => void;
57
135
  /**
58
- * Callback when mouse enters an item
136
+ * Callback when mouse enters an item (row-level)
59
137
  */
60
138
  onItemMouseEnter?: (item: ListItem) => void;
139
+ /**
140
+ * Callback when mouse leaves an item (row-level)
141
+ */
142
+ onItemMouseLeave?: (item: ListItem) => void;
61
143
  /**
62
144
  * Callback when column is clicked for sorting
63
145
  */
@@ -70,6 +152,47 @@ export interface ListViewProps {
70
152
  * Height of the list view
71
153
  */
72
154
  height?: number | string;
155
+ /**
156
+ * Custom classes for targeting sub-elements
157
+ */
158
+ classes?: ListViewClasses;
159
+ /**
160
+ * Override row rendering
161
+ * @param item - The list item
162
+ * @param state - Row state (selected, hovered, index)
163
+ * @param defaultProps - Props to spread on custom element for accessibility
164
+ * @returns Custom row element (fully replaces default)
165
+ */
166
+ renderRow?: (item: ListItem, state: RowRenderState, defaultProps: RowDefaultProps) => React.ReactNode;
167
+ /**
168
+ * Override cell rendering
169
+ * @param value - Cell value (item[columnKey])
170
+ * @param item - Full item object
171
+ * @param column - Column definition
172
+ * @param state - Cell state (hovered, selected row, indices)
173
+ * @returns Custom cell content (fully replaces default)
174
+ */
175
+ renderCell?: (value: any, item: ListItem, column: ListColumn, state: CellRenderState) => React.ReactNode;
176
+ /**
177
+ * Override header cell rendering
178
+ * @param column - Column definition
179
+ * @param state - Header state (sorted, direction)
180
+ * @param defaultProps - Props to spread on custom element
181
+ * @returns Custom header cell element (fully replaces default)
182
+ */
183
+ renderHeaderCell?: (column: ListColumn, state: HeaderCellRenderState, defaultProps: HeaderCellDefaultProps) => React.ReactNode;
184
+ /**
185
+ * Callback when a cell is clicked
186
+ */
187
+ onCellClick?: (item: ListItem, column: ListColumn, event: React.MouseEvent) => void;
188
+ /**
189
+ * Callback when mouse enters a cell
190
+ */
191
+ onCellMouseEnter?: (item: ListItem, column: ListColumn) => void;
192
+ /**
193
+ * Callback when mouse leaves a cell
194
+ */
195
+ onCellMouseLeave?: (item: ListItem, column: ListColumn) => void;
73
196
  }
74
197
  /**
75
198
  * Mac OS 9 style ListView component
@@ -1,2 +1,2 @@
1
1
  export { ListView, ListView as default } from './ListView';
2
- export type { ListViewProps, ListColumn, ListItem } from './ListView';
2
+ export type { ListViewProps, ListColumn, ListItem, ListViewClasses, RowRenderState, RowDefaultProps, CellRenderState, HeaderCellRenderState, HeaderCellDefaultProps } from './ListView';
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ export interface MenuDropdownProps {
3
+ /**
4
+ * Menu label (displayed in the menu bar/button)
5
+ */
6
+ label: React.ReactNode;
7
+ /**
8
+ * Menu items (content of the dropdown)
9
+ */
10
+ items: React.ReactNode;
11
+ /**
12
+ * Whether the menu is disabled
13
+ * @default false
14
+ */
15
+ disabled?: boolean;
16
+ /**
17
+ * Custom class name for the menu container
18
+ */
19
+ className?: string;
20
+ /**
21
+ * Custom class name for menu dropdown
22
+ */
23
+ dropdownClassName?: string;
24
+ /**
25
+ * Alignment of the dropdown menu
26
+ * @default 'left'
27
+ */
28
+ align?: 'left' | 'right';
29
+ }
30
+ /**
31
+ * Mac OS 9 style MenuDropdown component
32
+ *
33
+ * A standalone dropdown menu that shares the styling of the MenuBar.
34
+ * Useful for placing menus in the status area (rightContent) or other parts of the app.
35
+ */
36
+ export declare const MenuDropdown: React.FC<MenuDropdownProps>;
@@ -53,6 +53,10 @@ export interface MenuItemProps {
53
53
  * @default false
54
54
  */
55
55
  hasSubmenu?: boolean;
56
+ /**
57
+ * Submenu items
58
+ */
59
+ items?: React.ReactNode;
56
60
  }
57
61
  /**
58
62
  * Mac OS 9 style MenuItem component
@@ -1,3 +1,4 @@
1
1
  export { MenuBar, type MenuBarProps, type Menu } from './MenuBar';
2
2
  export { MenuItem, type MenuItemProps } from './MenuItem';
3
+ export { MenuDropdown, type MenuDropdownProps } from './MenuDropdown';
3
4
  export { default } from './MenuBar';
@@ -1,4 +1,22 @@
1
1
  import React from 'react';
2
+ import { WindowPosition } from '../../types';
3
+ /**
4
+ * Classes for targeting Window sub-elements
5
+ */
6
+ export interface WindowClasses {
7
+ /** Root container */
8
+ root?: string;
9
+ /** Title bar */
10
+ titleBar?: string;
11
+ /** Title text */
12
+ titleText?: string;
13
+ /** Window controls container */
14
+ controls?: string;
15
+ /** Individual control button */
16
+ controlButton?: string;
17
+ /** Content area */
18
+ content?: string;
19
+ }
2
20
  export interface WindowProps {
3
21
  /**
4
22
  * Window content
@@ -36,6 +54,10 @@ export interface WindowProps {
36
54
  * Custom class name for the content area
37
55
  */
38
56
  contentClassName?: string;
57
+ /**
58
+ * Custom classes for targeting sub-elements
59
+ */
60
+ classes?: WindowClasses;
39
61
  /**
40
62
  * Whether to show window controls (close, minimize, maximize)
41
63
  * @default true
@@ -62,6 +84,27 @@ export interface WindowProps {
62
84
  * @default false
63
85
  */
64
86
  resizable?: boolean;
87
+ /**
88
+ * Whether the window can be dragged by its title bar
89
+ * Window starts in normal flow and becomes absolutely positioned when dragged
90
+ * @default false
91
+ */
92
+ draggable?: boolean;
93
+ /**
94
+ * Initial position for draggable windows (uncontrolled)
95
+ * Only used when draggable is true
96
+ */
97
+ defaultPosition?: WindowPosition;
98
+ /**
99
+ * Controlled position for draggable windows
100
+ * Only used when draggable is true
101
+ */
102
+ position?: WindowPosition;
103
+ /**
104
+ * Callback when window position changes (during drag)
105
+ * Only called when draggable is true
106
+ */
107
+ onPositionChange?: (position: WindowPosition) => void;
65
108
  }
66
109
  /**
67
110
  * Mac OS 9 style Window component
@@ -74,6 +117,7 @@ export interface WindowProps {
74
117
  * - Active/inactive states
75
118
  * - Composable with custom TitleBar component
76
119
  * - Flexible sizing
120
+ * - Draggable windows (optional) - drag by title bar
77
121
  *
78
122
  * @example
79
123
  * ```tsx
@@ -95,6 +139,31 @@ export interface WindowProps {
95
139
  * >
96
140
  * <p>Content</p>
97
141
  * </Window>
142
+ *
143
+ * // Draggable window (uncontrolled)
144
+ * <Window title="Draggable" draggable>
145
+ * <p>Drag me by the title bar!</p>
146
+ * </Window>
147
+ *
148
+ * // Draggable window with initial position
149
+ * <Window
150
+ * title="Positioned"
151
+ * draggable
152
+ * defaultPosition={{ x: 100, y: 100 }}
153
+ * >
154
+ * <p>Starts at a specific position</p>
155
+ * </Window>
156
+ *
157
+ * // Controlled draggable window
158
+ * const [pos, setPos] = useState({ x: 0, y: 0 });
159
+ * <Window
160
+ * title="Controlled"
161
+ * draggable
162
+ * position={pos}
163
+ * onPositionChange={setPos}
164
+ * >
165
+ * <p>Parent controls position</p>
166
+ * </Window>
98
167
  * ```
99
168
  */
100
169
  export declare const Window: React.ForwardRefExoticComponent<WindowProps & React.RefAttributes<HTMLDivElement>>;
@@ -1,2 +1,2 @@
1
- export { Window, type WindowProps } from './Window';
1
+ export { Window, type WindowProps, type WindowClasses } from './Window';
2
2
  export { default } from './Window';
@@ -7,12 +7,13 @@ export { Radio, type RadioProps } from './components/Radio';
7
7
  export { TextField, type TextFieldProps } from './components/TextField';
8
8
  export { Select, type SelectProps, type SelectOption } from './components/Select';
9
9
  export { Tabs, TabPanel, type TabsProps, type TabPanelProps } from './components/Tabs';
10
- export { Window, type WindowProps } from './components/Window';
10
+ export { Window, type WindowProps, type WindowClasses } from './components/Window';
11
11
  export { Dialog, type DialogProps } from './components/Dialog';
12
- export { MenuBar, MenuItem, type MenuBarProps, type MenuItemProps, type Menu } from './components/MenuBar';
12
+ export { MenuBar, MenuItem, MenuDropdown, type MenuBarProps, type MenuItemProps, type Menu, type MenuDropdownProps } from './components/MenuBar';
13
13
  export { Scrollbar, type ScrollbarProps } from './components/Scrollbar';
14
- export { ListView, type ListViewProps, type ListColumn, type ListItem } from './components/ListView';
15
- export { FolderList, type FolderListProps } from './components/FolderList';
14
+ export { ListView, type ListViewProps, type ListColumn, type ListItem, type ListViewClasses, type RowRenderState, type RowDefaultProps, type CellRenderState, type HeaderCellRenderState, type HeaderCellDefaultProps } from './components/ListView';
15
+ export { FolderList, type FolderListProps, type FolderListClasses } from './components/FolderList';
16
16
  export * from './components/Icon/categories';
17
17
  export * from './tokens';
18
+ export { mergeClasses, createClassBuilder } from './utils/classNames';
18
19
  export type * from './types';
@@ -1,14 +1,41 @@
1
+ /**
2
+ * Generic classes object for targeting sub-elements within components
3
+ * Components extend this with specific element keys
4
+ */
5
+ export interface ComponentClasses {
6
+ root?: string;
7
+ [key: string]: string | undefined;
8
+ }
1
9
  /**
2
10
  * Base component props that all components should extend
11
+ * @template TClasses - Specific classes type for the component
3
12
  */
4
- export interface BaseComponentProps {
5
- /** Additional CSS class name */
13
+ export interface BaseComponentProps<TClasses extends ComponentClasses = ComponentClasses> {
14
+ /** Additional CSS class name for root element */
6
15
  className?: string;
7
16
  /** Inline styles */
8
17
  style?: React.CSSProperties;
18
+ /** Custom classes for targeting sub-elements */
19
+ classes?: TClasses;
9
20
  /** Test ID for testing purposes */
10
21
  'data-testid'?: string;
11
22
  }
23
+ /**
24
+ * Common render state interface for render prop patterns
25
+ * Provides information about element state for conditional rendering
26
+ */
27
+ export interface RenderState {
28
+ /** Whether the element is being hovered */
29
+ isHovered?: boolean;
30
+ /** Whether the element is selected */
31
+ isSelected?: boolean;
32
+ /** Whether the element is in active state (e.g., pressed) */
33
+ isActive?: boolean;
34
+ /** Whether the element has keyboard focus */
35
+ isFocused?: boolean;
36
+ /** Whether the element is disabled */
37
+ isDisabled?: boolean;
38
+ }
12
39
  /**
13
40
  * Common variant types for Mac OS 9 components
14
41
  */
@@ -21,6 +48,13 @@ export type Size = 'small' | 'medium' | 'large';
21
48
  * Common state types
22
49
  */
23
50
  export type State = 'default' | 'hover' | 'active' | 'disabled' | 'focused';
51
+ /**
52
+ * Window position for draggable windows
53
+ */
54
+ export interface WindowPosition {
55
+ x: number;
56
+ y: number;
57
+ }
24
58
  /**
25
59
  * Component ref types
26
60
  */
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Merges multiple class names into a single string
3
+ * Filters out undefined, null, false, and empty strings
4
+ *
5
+ * @param classes - Class names to merge
6
+ * @returns Merged class name string
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * mergeClasses('base', isActive && 'active', undefined, 'custom')
11
+ * // Returns: "base active custom"
12
+ * ```
13
+ */
14
+ export declare const mergeClasses: (...classes: (string | undefined | false | null)[]) => string;
15
+ /**
16
+ * Creates a class name builder function with a base class
17
+ * Useful for component-level class management
18
+ *
19
+ * @param baseClass - Base class name
20
+ * @returns Function that merges additional classes with base
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const cn = createClassBuilder('button');
25
+ * cn('primary', isDisabled && 'disabled')
26
+ * // Returns: "button primary disabled"
27
+ * ```
28
+ */
29
+ export declare const createClassBuilder: (baseClass: string) => (...additionalClasses: (string | undefined | false | null)[]) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/mac-os9-ui",
3
- "version": "0.2.21",
3
+ "version": "0.2.24",
4
4
  "description": "A pixel-perfect Mac OS 9 UI component library for React and TypeScript",
5
5
  "private": false,
6
6
  "sideEffects": [
Binary file
Binary file
Binary file
Binary file