@liiift-studio/mac-os9-ui 0.2.25 → 0.3.1

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 CHANGED
@@ -177,16 +177,43 @@ function MyComponent() {
177
177
 
178
178
  ## Styling
179
179
 
180
- Import the styles **once** in your application's entry point:
180
+ ### Basic Setup
181
+
182
+ Import the component styles **once** in your application's entry point:
181
183
 
182
184
  ```tsx
183
185
  // In your app's main file (e.g., main.tsx, _app.tsx, index.tsx)
184
186
  import '@liiift-studio/mac-os9-ui/styles';
185
187
  ```
186
188
 
189
+ This provides:
190
+ - CSS custom properties (design tokens/variables)
191
+ - Font declarations (Pixel font family)
192
+ - Component styles
193
+ - Utility classes
194
+
187
195
  This needs to be done only once at the root of your application. All components will then have the correct Mac OS 9 styles applied.
188
196
 
189
- All components use CSS Modules internally, so styles are scoped and won't conflict with your application's CSS. The theme variables and global styles are extracted to a separate CSS file for optimal caching and performance.
197
+ ### Optional Global Styles
198
+
199
+ If you want the **full Mac OS 9 experience** with global styles applied to your entire application (body background, typography, box-sizing reset), you can optionally import the base styles:
200
+
201
+ ```tsx
202
+ // In your app's main file
203
+ import '@liiift-studio/mac-os9-ui/styles'; // Required
204
+ import '@liiift-studio/mac-os9-ui/base'; // Optional global styles
205
+ ```
206
+
207
+ The optional base styles include:
208
+ - Universal `box-sizing: border-box` reset
209
+ - Responsive typography scaling on `<html>`
210
+ - Body styles (margin, padding, font-family, colors)
211
+
212
+ **Note:** Only import `/base` if you want these global styles. The library is designed to work without polluting your application's global styles, making it easier to integrate into existing projects.
213
+
214
+ ### CSS Modules
215
+
216
+ All components use CSS Modules internally, so styles are scoped and won't conflict with your application's CSS. The theme variables and component styles are extracted to separate CSS files for optimal caching and performance.
190
217
 
191
218
  ## TypeScript Support
192
219
 
package/dist/base.css ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Optional Base Styles for Mac OS 9 UI
3
+ *
4
+ * This file contains global element styles (html, body, box-sizing reset)
5
+ * that create the full Mac OS 9 experience.
6
+ *
7
+ * IMPORTANT: Only import this if you want these styles applied globally
8
+ * to your entire application.
9
+ *
10
+ * Usage:
11
+ * import '@liiift-studio/mac-os9-ui/styles'; // Required
12
+ * import '@liiift-studio/mac-os9-ui/base'; // Optional global styles
13
+ *
14
+ * What this includes:
15
+ * - Universal box-sizing reset
16
+ * - Responsive typography scaling on <html>
17
+ * - Base body styles (margin, padding, font, color)
18
+ */
19
+
20
+ /* ========================================
21
+ * Universal Box Sizing
22
+ * ======================================== */
23
+
24
+ *,
25
+ *::before,
26
+ *::after {
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ /* ========================================
31
+ * HTML Base Styles
32
+ * Mobile-first responsive typography
33
+ * ======================================== */
34
+
35
+ html {
36
+ /* Mobile/default: 14px base - optimal for mobile devices */
37
+ font-size: 14px;
38
+ -webkit-font-smoothing: antialiased;
39
+ -moz-osx-font-smoothing: grayscale;
40
+ }
41
+
42
+ /* ========================================
43
+ * Responsive Typography
44
+ * Scale base font size at different breakpoints
45
+ * Following mobile-first best practices:
46
+ * - Mobile: 14px (better for small screens, maintains Mac OS 9 small text aesthetic)
47
+ * - Tablet: 16px (standard web default)
48
+ * - Desktop: 16px (comfortable for productivity)
49
+ * - Large Desktop: 18px (takes advantage of larger screens)
50
+ * ======================================== */
51
+
52
+ /* Small mobile: 480px+ */
53
+
54
+ @media (min-width: 480px) {
55
+ html {
56
+ font-size: 15px;
57
+ }
58
+ }
59
+
60
+ /* Tablet: 768px+ */
61
+
62
+ @media (min-width: 768px) {
63
+ html {
64
+ font-size: 16px;
65
+ }
66
+ }
67
+
68
+ /* Desktop: 1024px+ */
69
+
70
+ @media (min-width: 1024px) {
71
+ html {
72
+ font-size: 16px;
73
+ }
74
+ }
75
+
76
+ /* Large Desktop/XL: 1440px+ */
77
+
78
+ @media (min-width: 1440px) {
79
+ html {
80
+ font-size: 18px;
81
+ }
82
+ }
83
+
84
+ /* ========================================
85
+ * Body Base Styles
86
+ * Mac OS 9 default appearance
87
+ * ======================================== */
88
+
89
+ body {
90
+ margin: 0;
91
+ padding: 0;
92
+ font-family: var(--font-system);
93
+ font-size: var(--font-size-md);
94
+ line-height: var(--line-height-normal);
95
+ color: var(--color-text);
96
+ background-color: var(--color-background);
97
+ }
98
+
99
+ /*# sourceMappingURL=base.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["base.css"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;EAiBE;;AAEF;;6CAE6C;;AAC7C;;;CAGC,sBAAsB;AACvB;;AAEA;;;6CAG6C;;AAC7C;CACC,2DAA2D;CAC3D,eAAe;CACf,mCAAmC;CACnC,kCAAkC;AACnC;;AAEA;;;;;;;;6CAQ6C;;AAE7C,yBAAyB;;AACzB;CACC;EACC,eAAe;CAChB;AACD;;AAEA,mBAAmB;;AACnB;CACC;EACC,eAAe;CAChB;AACD;;AAEA,qBAAqB;;AACrB;CACC;EACC,eAAe;CAChB;AACD;;AAEA,8BAA8B;;AAC9B;CACC;EACC,eAAe;CAChB;AACD;;AAEA;;;6CAG6C;;AAC7C;CACC,SAAS;CACT,UAAU;CACV,+BAA+B;CAC/B,8BAA8B;CAC9B,sCAAsC;CACtC,wBAAwB;CACxB,yCAAyC;AAC1C","file":"base.css","sourcesContent":["/**\n * Optional Base Styles for Mac OS 9 UI\n * \n * This file contains global element styles (html, body, box-sizing reset)\n * that create the full Mac OS 9 experience.\n * \n * IMPORTANT: Only import this if you want these styles applied globally\n * to your entire application.\n * \n * Usage:\n * import '@liiift-studio/mac-os9-ui/styles'; // Required\n * import '@liiift-studio/mac-os9-ui/base'; // Optional global styles\n * \n * What this includes:\n * - Universal box-sizing reset\n * - Responsive typography scaling on <html>\n * - Base body styles (margin, padding, font, color)\n */\n\n/* ========================================\n * Universal Box Sizing\n * ======================================== */\n*,\n*::before,\n*::after {\n\tbox-sizing: border-box;\n}\n\n/* ========================================\n * HTML Base Styles\n * Mobile-first responsive typography\n * ======================================== */\nhtml {\n\t/* Mobile/default: 14px base - optimal for mobile devices */\n\tfont-size: 14px;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n/* ========================================\n * Responsive Typography\n * Scale base font size at different breakpoints\n * Following mobile-first best practices:\n * - Mobile: 14px (better for small screens, maintains Mac OS 9 small text aesthetic)\n * - Tablet: 16px (standard web default)\n * - Desktop: 16px (comfortable for productivity)\n * - Large Desktop: 18px (takes advantage of larger screens)\n * ======================================== */\n\n/* Small mobile: 480px+ */\n@media (min-width: 480px) {\n\thtml {\n\t\tfont-size: 15px;\n\t}\n}\n\n/* Tablet: 768px+ */\n@media (min-width: 768px) {\n\thtml {\n\t\tfont-size: 16px;\n\t}\n}\n\n/* Desktop: 1024px+ */\n@media (min-width: 1024px) {\n\thtml {\n\t\tfont-size: 16px;\n\t}\n}\n\n/* Large Desktop/XL: 1440px+ */\n@media (min-width: 1440px) {\n\thtml {\n\t\tfont-size: 18px;\n\t}\n}\n\n/* ========================================\n * Body Base Styles\n * Mac OS 9 default appearance\n * ======================================== */\nbody {\n\tmargin: 0;\n\tpadding: 0;\n\tfont-family: var(--font-system);\n\tfont-size: var(--font-size-md);\n\tline-height: var(--line-height-normal);\n\tcolor: var(--color-text);\n\tbackground-color: var(--color-background);\n}\n"]}
package/dist/index.cjs CHANGED
@@ -818,14 +818,24 @@ var styles$6 = {"window":"Window-module_window","window--active":"Window-module_
818
818
  * </Window>
819
819
  * ```
820
820
  */
821
- const Window = React.forwardRef(({ children, title, titleBar, active = true, width = 'auto', height = 'auto', className = '', contentClassName = '', classes, showControls = true, onClose, onMinimize, onMaximize, onMouseEnter, resizable = false, draggable = false, defaultPosition, position: controlledPosition, onPositionChange, }, ref) => {
821
+ const Window = React.forwardRef(({ children, title, titleBar, active = true, width = 'auto', height = 'auto', className = '', contentClassName = '', classes, showControls = true, onClose, onMinimize, onMaximize, onMouseEnter, resizable = false, minWidth = 200, minHeight = 100, maxWidth, maxHeight, onResize, draggable = false, defaultPosition, position: controlledPosition, onPositionChange, }, ref) => {
822
822
  // Drag state management
823
823
  const [internalPosition, setInternalPosition] = React.useState(defaultPosition || null);
824
824
  const [isDragging, setIsDragging] = React.useState(false);
825
825
  const [hasBeenDragged, setHasBeenDragged] = React.useState(!!(defaultPosition || controlledPosition));
826
826
  const dragStartRef = React.useRef(null);
827
+ // Resize state management
828
+ const [internalSize, setInternalSize] = React.useState({
829
+ width,
830
+ height,
831
+ });
832
+ const [isResizing, setIsResizing] = React.useState(false);
833
+ const resizeStartRef = React.useRef(null);
827
834
  // Use controlled position if provided, otherwise use internal state
828
835
  const currentPosition = controlledPosition || internalPosition;
836
+ // Use internal size state for resize tracking
837
+ const currentWidth = isResizing ? internalSize.width : width;
838
+ const currentHeight = isResizing ? internalSize.height : height;
829
839
  // Handle mouse down on title bar to start dragging
830
840
  const handleTitleBarMouseDown = React.useCallback((event) => {
831
841
  if (!draggable)
@@ -850,6 +860,69 @@ const Window = React.forwardRef(({ children, title, titleBar, active = true, wid
850
860
  };
851
861
  setIsDragging(true);
852
862
  }, [draggable]);
863
+ // Handle mouse down on resize handle to start resizing
864
+ const handleResizeMouseDown = React.useCallback((event) => {
865
+ if (!resizable)
866
+ return;
867
+ event.preventDefault();
868
+ event.stopPropagation();
869
+ const windowElement = event.currentTarget.closest(`.${styles$6.window}`);
870
+ if (!windowElement)
871
+ return;
872
+ const rect = windowElement.getBoundingClientRect();
873
+ // Store resize start info
874
+ resizeStartRef.current = {
875
+ width: rect.width,
876
+ height: rect.height,
877
+ mouseX: event.clientX,
878
+ mouseY: event.clientY,
879
+ };
880
+ setIsResizing(true);
881
+ }, [resizable]);
882
+ // Handle mouse move during resize
883
+ React.useEffect(() => {
884
+ if (!isResizing || !resizeStartRef.current)
885
+ return;
886
+ const handleMouseMove = (event) => {
887
+ event.preventDefault();
888
+ if (!resizeStartRef.current)
889
+ return;
890
+ // Calculate delta
891
+ const deltaX = event.clientX - resizeStartRef.current.mouseX;
892
+ const deltaY = event.clientY - resizeStartRef.current.mouseY;
893
+ // Calculate new size
894
+ let newWidth = resizeStartRef.current.width + deltaX;
895
+ let newHeight = resizeStartRef.current.height + deltaY;
896
+ // Apply constraints
897
+ if (newWidth < minWidth)
898
+ newWidth = minWidth;
899
+ if (newHeight < minHeight)
900
+ newHeight = minHeight;
901
+ if (maxWidth && newWidth > maxWidth)
902
+ newWidth = maxWidth;
903
+ if (maxHeight && newHeight > maxHeight)
904
+ newHeight = maxHeight;
905
+ // Update size
906
+ setInternalSize({
907
+ width: newWidth,
908
+ height: newHeight,
909
+ });
910
+ // Call callback if provided
911
+ if (onResize) {
912
+ onResize({ width: newWidth, height: newHeight });
913
+ }
914
+ };
915
+ const handleMouseUp = () => {
916
+ setIsResizing(false);
917
+ resizeStartRef.current = null;
918
+ };
919
+ document.addEventListener('mousemove', handleMouseMove);
920
+ document.addEventListener('mouseup', handleMouseUp);
921
+ return () => {
922
+ document.removeEventListener('mousemove', handleMouseMove);
923
+ document.removeEventListener('mouseup', handleMouseUp);
924
+ };
925
+ }, [isResizing, minWidth, minHeight, maxWidth, maxHeight, onResize]);
853
926
  // Handle mouse move during drag
854
927
  React.useEffect(() => {
855
928
  if (!isDragging || !dragStartRef.current)
@@ -907,11 +980,13 @@ const Window = React.forwardRef(({ children, title, titleBar, active = true, wid
907
980
  const titleBarClassNames = mergeClasses(styles$6.titleBar, draggable && styles$6['titleBar--draggable'], isDragging && styles$6['titleBar--dragging'], classes?.titleBar);
908
981
  // Window style
909
982
  const windowStyle = {};
910
- if (width !== 'auto') {
911
- windowStyle.width = typeof width === 'number' ? `${width}px` : width;
983
+ // Apply width - use currentWidth during resize, otherwise use prop
984
+ if (currentWidth !== 'auto') {
985
+ windowStyle.width = typeof currentWidth === 'number' ? `${currentWidth}px` : currentWidth;
912
986
  }
913
- if (height !== 'auto') {
914
- windowStyle.height = typeof height === 'number' ? `${height}px` : height;
987
+ // Apply height - use currentHeight during resize, otherwise use prop
988
+ if (currentHeight !== 'auto') {
989
+ windowStyle.height = typeof currentHeight === 'number' ? `${currentHeight}px` : currentHeight;
915
990
  }
916
991
  // Apply position if draggable and has been dragged
917
992
  if (draggable && hasBeenDragged && currentPosition) {
@@ -929,7 +1004,7 @@ const Window = React.forwardRef(({ children, title, titleBar, active = true, wid
929
1004
  }
930
1005
  return null;
931
1006
  };
932
- return (jsxRuntime.jsxs("div", { ref: ref, className: windowClassNames, style: windowStyle, onMouseEnter: onMouseEnter, children: [renderTitleBar(), jsxRuntime.jsx("div", { className: contentClassNames, children: children }), resizable && jsxRuntime.jsx("div", { className: styles$6.resizeHandle, "aria-hidden": "true" })] }));
1007
+ return (jsxRuntime.jsxs("div", { ref: ref, className: windowClassNames, style: windowStyle, onMouseEnter: onMouseEnter, children: [renderTitleBar(), jsxRuntime.jsx("div", { className: contentClassNames, children: children }), resizable && (jsxRuntime.jsx("div", { className: styles$6.resizeHandle, onMouseDown: handleResizeMouseDown, "aria-hidden": "true" }))] }));
933
1008
  });
934
1009
  Window.displayName = 'Window';
935
1010