From 1af1ca3646ac0b7fe873685f16651977326e1c9b Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Thu, 7 Aug 2025 22:13:08 +0300 Subject: [PATCH 001/103] fix: update breakpoint management to use CSS custom properties --- packages/core/src/contexts/LayoutProvider.tsx | 42 +++++++++++-------- packages/core/src/styles/breakpoints.scss | 12 ++++-- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/core/src/contexts/LayoutProvider.tsx b/packages/core/src/contexts/LayoutProvider.tsx index 425b7b3..a1b2763 100644 --- a/packages/core/src/contexts/LayoutProvider.tsx +++ b/packages/core/src/contexts/LayoutProvider.tsx @@ -68,23 +68,31 @@ const LayoutProvider: React.FC = ({ }; useEffect(() => { - // Initialize width - const updateWidth = () => { - const newWidth = window.innerWidth; - setWidth(newWidth); - setCurrentBreakpoint(getCurrentBreakpoint(newWidth)); - }; - - // Set initial width - updateWidth(); - - // Add resize listener - window.addEventListener('resize', updateWidth); - - return () => { - window.removeEventListener('resize', updateWidth); - }; - }, [breakpoints]); + // Update CSS custom properties + const root = document.documentElement; + Object.entries(breakpoints).forEach(([key, value]) => { + if (value !== Infinity) { + root.style.setProperty(`--breakpoint-${key}`, `${value}px`); + } + }); + + // Initialize width + const updateWidth = () => { + const newWidth = window.innerWidth; + setWidth(newWidth); + setCurrentBreakpoint(getCurrentBreakpoint(newWidth)); + }; + + // Set initial width + updateWidth(); + + // Add resize listener + window.addEventListener('resize', updateWidth); + + return () => { + window.removeEventListener('resize', updateWidth); + }; + }, [breakpoints]); const value: LayoutContextType = { currentBreakpoint, diff --git a/packages/core/src/styles/breakpoints.scss b/packages/core/src/styles/breakpoints.scss index 1defea0..648625c 100644 --- a/packages/core/src/styles/breakpoints.scss +++ b/packages/core/src/styles/breakpoints.scss @@ -1,6 +1,12 @@ -$breakpoint-s: 768px; -$breakpoint-m: 1024px; -$breakpoint-l: 1440px; +:root { + --breakpoint-s: 768px; + --breakpoint-m: 1024px; + --breakpoint-l: 1440px; +} + +$breakpoint-s: var(--breakpoint-s); +$breakpoint-m: var(--breakpoint-m); +$breakpoint-l: var(--breakpoint-l); @mixin s { @media (max-width: #{$breakpoint-s}) { From 3b8a9ff8f1810fa57c28fac3992e305441ad4e45 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Fri, 8 Aug 2025 00:20:52 +0300 Subject: [PATCH 002/103] fix: add xs breakpoint support and related utility classes --- packages/core/src/components/Flex.tsx | 7 +- packages/core/src/components/ServerFlex.tsx | 11 + packages/core/src/styles/breakpoints.scss | 8 + packages/core/src/styles/display.scss | 38 +++ packages/core/src/styles/flex.scss | 87 ++++++ packages/core/src/styles/grid.scss | 58 ++++ packages/core/src/styles/position.scss | 310 ++++++++++++++++++++ packages/core/src/styles/typography.scss | 6 + 8 files changed, 524 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/Flex.tsx b/packages/core/src/components/Flex.tsx index 0e863db..67954a9 100644 --- a/packages/core/src/components/Flex.tsx +++ b/packages/core/src/components/Flex.tsx @@ -66,10 +66,15 @@ const Flex = forwardRef(({ cursor, xl, l, m, s, // Check xs breakpoint if (currentBreakpoint === 'xs') { - // For xs, we cascade down from all larger breakpoints + // If xs.hide is explicitly set, use that value + if (xs?.hide !== undefined) return xs.hide === true; + // Otherwise check if s.hide is set (cascading down) if (s?.hide !== undefined) return s.hide === true; + // Otherwise check if m.hide is set (cascading down) if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop return hide === true; } diff --git a/packages/core/src/components/ServerFlex.tsx b/packages/core/src/components/ServerFlex.tsx index 2cc8037..f72df66 100644 --- a/packages/core/src/components/ServerFlex.tsx +++ b/packages/core/src/components/ServerFlex.tsx @@ -179,10 +179,12 @@ const ServerFlex = forwardRef( l?.position && `l-position-${l.position}`, m?.position && `m-position-${m.position}`, s?.position && `s-position-${s.position}`, + xs?.position && `xs-position-${xs.position}`, hide && "flex-hide", l?.hide && "l-flex-hide", m?.hide && "m-flex-hide", s?.hide && "s-flex-hide", + xs?.hide && "xs-flex-hide", padding && `p-${padding}`, paddingLeft && `pl-${paddingLeft}`, paddingRight && `pr-${paddingRight}`, @@ -238,6 +240,7 @@ const ServerFlex = forwardRef( l?.direction && `l-flex-${l.direction}`, m?.direction && `m-flex-${m.direction}`, s?.direction && `s-flex-${s.direction}`, + xs?.direction && `xs-flex-${xs.direction}`, pointerEvents && `pointer-events-${pointerEvents}`, transition && `transition-${transition}`, opacity && `opacity-${opacity}`, @@ -278,6 +281,14 @@ const ServerFlex = forwardRef( (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined ? `s-align-${s.vertical}` : `s-justify-${s.vertical}`), + xs?.horizontal && + (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined + ? `xs-justify-${xs.horizontal}` + : `xs-align-${xs.horizontal}`), + xs?.vertical && + (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined + ? `xs-align-${xs.vertical}` + : `xs-justify-${xs.vertical}`), center && "center", fit && "fit", fitWidth && "fit-width", diff --git a/packages/core/src/styles/breakpoints.scss b/packages/core/src/styles/breakpoints.scss index 648625c..c80808c 100644 --- a/packages/core/src/styles/breakpoints.scss +++ b/packages/core/src/styles/breakpoints.scss @@ -1,13 +1,21 @@ :root { + --breakpoint-xs: 480px; --breakpoint-s: 768px; --breakpoint-m: 1024px; --breakpoint-l: 1440px; } +$breakpoint-xs: var(--breakpoint-xs); $breakpoint-s: var(--breakpoint-s); $breakpoint-m: var(--breakpoint-m); $breakpoint-l: var(--breakpoint-l); +@mixin xs { + @media (max-width: #{$breakpoint-xs}) { + @content; + } +} + @mixin s { @media (max-width: #{$breakpoint-s}) { @content; diff --git a/packages/core/src/styles/display.scss b/packages/core/src/styles/display.scss index 5f06098..840a896 100644 --- a/packages/core/src/styles/display.scss +++ b/packages/core/src/styles/display.scss @@ -150,6 +150,44 @@ } } +@include breakpoints.xs { + .xs-overflow-auto { + overflow: auto; + } + + .xs-overflow-x-scroll{ + overflow-x: scroll; + } + + .xs-overflow-x-auto { + overflow-x: auto; + } + + .xs-overflow-y-auto{ + overflow-y: auto; + } + + .xs-overflow-y-scroll{ + overflow-y: scroll; + } + + .xs-overflow-hidden { + overflow: hidden; + } + + .xs-overflow-scroll{ + overflow: scroll; + } + + .xs-overflow-x-hidden { + overflow-x: hidden; + } + + .xs-overflow-y-hidden{ + overflow-y: hidden; + } +} + .opacity-0 { opacity: 0; } diff --git a/packages/core/src/styles/flex.scss b/packages/core/src/styles/flex.scss index af9c1a3..0bffd4d 100644 --- a/packages/core/src/styles/flex.scss +++ b/packages/core/src/styles/flex.scss @@ -428,4 +428,91 @@ align-items: center; justify-content: center; } +} + +@include breakpoints.xs { + .xs-flex-hide { + display: none; + } + + .xs-flex-show { + display: flex; + } + + .xs-flex-column { + flex-direction: column; + } + + .xs-flex-row { + flex-direction: row; + } + + .xs-flex-column-reverse { + flex-direction: column-reverse; + } + + .xs-flex-row-reverse { + flex-direction: row-reverse; + } + + .xs-justify-start { + justify-content: flex-start; + } + + .xs-justify-center { + justify-content: center; + } + + .xs-justify-end { + justify-content: flex-end; + } + + .xs-justify-between { + justify-content: space-between; + } + + .xs-justify-around { + justify-content: space-around; + } + + .xs-justify-even { + justify-content: space-evenly; + } + + .xs-justify-stretch { + justify-content: stretch; + } + + .xs-align-start { + align-items: flex-start; + } + + .xs-align-center { + align-items: center; + } + + .xs-align-end { + align-items: flex-end; + } + + .xs-align-between { + align-items: space-between; + } + + .xs-align-around { + align-items: space-around; + } + + .xs-align-even { + align-items: space-evenly; + } + + .xs-align-stretch { + align-items: stretch; + } + + .xs-center { + align-items: center; + justify-content: center; + } } \ No newline at end of file diff --git a/packages/core/src/styles/grid.scss b/packages/core/src/styles/grid.scss index e0f89c8..83e88f6 100644 --- a/packages/core/src/styles/grid.scss +++ b/packages/core/src/styles/grid.scss @@ -232,4 +232,62 @@ .s-grid-show { display: grid; } +} + +@include breakpoints.xs { + .xs-columns-1 { + grid-template-columns: 1fr; + } + + .xs-columns-2 { + grid-template-columns: repeat(2, 1fr); + } + + .xs-columns-3 { + grid-template-columns: repeat(3, 1fr); + } + + .xs-columns-4 { + grid-template-columns: repeat(4, 1fr); + } + + .xs-columns-5 { + grid-template-columns: repeat(5, 1fr); + } + + .xs-columns-6 { + grid-template-columns: repeat(6, 1fr); + } + + .xs-columns-7 { + grid-template-columns: repeat(7, 1fr); + } + + .xs-columns-8 { + grid-template-columns: repeat(8, 1fr); + } + + .xs-columns-9 { + grid-template-columns: repeat(9, 1fr); + } + + .xs-columns-10 { + grid-template-columns: repeat(10, 1fr); + } + + .xs-columns-11 { + grid-template-columns: repeat(11, 1fr); + } + + .xs-columns-12 { + grid-template-columns: repeat(12, 1fr); + } + + .xs-grid-hide { + display: none; + } + + .xs-grid-show { + display: grid; + } } \ No newline at end of file diff --git a/packages/core/src/styles/position.scss b/packages/core/src/styles/position.scss index 981563f..f6f096d 100644 --- a/packages/core/src/styles/position.scss +++ b/packages/core/src/styles/position.scss @@ -1236,4 +1236,314 @@ .s-right-160 { right: var(--static-space-160); } +} + +@include breakpoints.xs { + .xs-position-relative { + position: relative; + } + + .xs-position-fixed { + position: fixed; + } + + .xs-position-absolute { + position: absolute; + } + + .xs-position-sticky { + position: sticky; + } + + .xs-position-static { + position: static; + } + + .xs-top-0 { + top: 0; + } + + .xs-left-0 { + left: 0; + } + + .xs-bottom-0 { + bottom: 0; + } + + .xs-right-0 { + right: 0; + } + + .xs-top-1 { + top: var(--static-space-1); + } + + .xs-left-1 { + left: var(--static-space-1); + } + + .xs-bottom-1 { + bottom: var(--static-space-1); + } + + .xs-right-1 { + right: var(--static-space-1); + } + + .xs-top-2 { + top: var(--static-space-2); + } + + .xs-left-2 { + left: var(--static-space-2); + } + + .xs-bottom-2 { + bottom: var(--static-space-2); + } + + .xs-right-2 { + right: var(--static-space-2); + } + + .xs-top-4 { + top: var(--static-space-4); + } + + .xs-left-4 { + left: var(--static-space-4); + } + + .xs-bottom-4 { + bottom: var(--static-space-4); + } + + .xs-right-4 { + right: var(--static-space-4); + } + + .xs-top-8 { + top: var(--static-space-8); + } + + .xs-left-8 { + left: var(--static-space-8); + } + + .xs-bottom-8 { + bottom: var(--static-space-8); + } + + .xs-right-8 { + right: var(--static-space-8); + } + + .xs-top-12 { + top: var(--static-space-12); + } + + .xs-left-12 { + left: var(--static-space-12); + } + + .xs-bottom-12 { + bottom: var(--static-space-12); + } + + .xs-right-12 { + right: var(--static-space-12); + } + + .xs-top-16 { + top: var(--static-space-16); + } + + .xs-left-16 { + left: var(--static-space-16); + } + + .xs-bottom-16 { + bottom: var(--static-space-16); + } + + .xs-right-16 { + right: var(--static-space-16); + } + + .xs-top-20 { + top: var(--static-space-20); + } + + .xs-left-20 { + left: var(--static-space-20); + } + + .xs-bottom-20 { + bottom: var(--static-space-20); + } + + .xs-right-20 { + right: var(--static-space-20); + } + + .xs-top-24 { + top: var(--static-space-24); + } + + .xs-left-24 { + left: var(--static-space-24); + } + + .xs-bottom-24 { + bottom: var(--static-space-24); + } + + .xs-right-24 { + right: var(--static-space-24); + } + + .xs-top-32 { + top: var(--static-space-32); + } + + .xs-left-32 { + left: var(--static-space-32); + } + + .xs-bottom-32 { + bottom: var(--static-space-32); + } + + .xs-right-32 { + right: var(--static-space-32); + } + + .xs-top-40 { + top: var(--static-space-40); + } + + .xs-left-40 { + left: var(--static-space-40); + } + + .xs-bottom-40 { + bottom: var(--static-space-40); + } + + .xs-right-40 { + right: var(--static-space-40); + } + + .xs-top-48 { + top: var(--static-space-48); + } + + .xs-left-48 { + left: var(--static-space-48); + } + + .xs-bottom-48 { + bottom: var(--static-space-48); + } + + .xs-right-48 { + right: var(--static-space-48); + } + + .xs-top-56 { + top: var(--static-space-56); + } + + .xs-left-56 { + left: var(--static-space-56); + } + + .xs-bottom-56 { + bottom: var(--static-space-56); + } + + .xs-right-56 { + right: var(--static-space-56); + } + + .xs-top-64 { + top: var(--static-space-64); + } + + .xs-left-64 { + left: var(--static-space-64); + } + + .xs-bottom-64 { + bottom: var(--static-space-64); + } + + .xs-right-64 { + right: var(--static-space-64); + } + + .xs-top-80 { + top: var(--static-space-80); + } + + .xs-left-80 { + left: var(--static-space-80); + } + + .xs-bottom-80 { + bottom: var(--static-space-80); + } + + .xs-right-80 { + right: var(--static-space-80); + } + + .xs-top-104 { + top: var(--static-space-104); + } + + .xs-left-104 { + left: var(--static-space-104); + } + + .xs-bottom-104 { + bottom: var(--static-space-104); + } + + .xs-right-104 { + right: var(--static-space-104); + } + + .xs-top-128 { + top: var(--static-space-128); + } + + .xs-left-128 { + left: var(--static-space-128); + } + + .xs-bottom-128 { + bottom: var(--static-space-128); + } + + .xs-right-128 { + right: var(--static-space-128); + } + + .xs-top-160 { + top: var(--static-space-160); + } + + .xs-left-160 { + left: var(--static-space-160); + } + + .xs-bottom-160 { + bottom: var(--static-space-160); + } + + .xs-right-160 { + right: var(--static-space-160); + } } \ No newline at end of file diff --git a/packages/core/src/styles/typography.scss b/packages/core/src/styles/typography.scss index 065eb9c..00088d8 100644 --- a/packages/core/src/styles/typography.scss +++ b/packages/core/src/styles/typography.scss @@ -19,6 +19,12 @@ html { } } +@include breakpoints.xs { + html { + font-size: var(--font-scaling-mobile); + } +} + h1, h2, h3, h4, h5, h6, p { margin: 0; } From 18550520baa088417abd5aa509754dc755fea232 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Fri, 8 Aug 2025 11:43:10 +0300 Subject: [PATCH 003/103] fix: moved breakpoints to global.scss to avoid errors during build --- packages/core/src/styles/breakpoints.scss | 7 ------- packages/core/src/styles/global.scss | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/styles/breakpoints.scss b/packages/core/src/styles/breakpoints.scss index c80808c..a583d5c 100644 --- a/packages/core/src/styles/breakpoints.scss +++ b/packages/core/src/styles/breakpoints.scss @@ -1,10 +1,3 @@ -:root { - --breakpoint-xs: 480px; - --breakpoint-s: 768px; - --breakpoint-m: 1024px; - --breakpoint-l: 1440px; -} - $breakpoint-xs: var(--breakpoint-xs); $breakpoint-s: var(--breakpoint-s); $breakpoint-m: var(--breakpoint-m); diff --git a/packages/core/src/styles/global.scss b/packages/core/src/styles/global.scss index 52b03df..59dfeed 100644 --- a/packages/core/src/styles/global.scss +++ b/packages/core/src/styles/global.scss @@ -11,6 +11,13 @@ img { user-select: none; } +:root { + --breakpoint-xs: 480px; + --breakpoint-s: 768px; + --breakpoint-m: 1024px; + --breakpoint-l: 1440px; +} + /* SELECTION */ ::selection { background: var(--neutral-on-background-medium); From 5d9c368d8203ec043276ee48c5c12b8cbf598467 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Fri, 8 Aug 2025 16:21:21 +0300 Subject: [PATCH 004/103] fix: implement useResponsiveClasses hook for dynamic class management --- packages/core/src/components/ClientFlex.tsx | 3 + packages/core/src/components/ClientGrid.tsx | 3 + .../core/src/hooks/useResponsiveClasses.ts | 171 ++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 packages/core/src/hooks/useResponsiveClasses.ts diff --git a/packages/core/src/components/ClientFlex.tsx b/packages/core/src/components/ClientFlex.tsx index e76e139..fbf2cd7 100644 --- a/packages/core/src/components/ClientFlex.tsx +++ b/packages/core/src/components/ClientFlex.tsx @@ -5,6 +5,7 @@ import { ServerFlex, Cursor } from "."; import { FlexProps, StyleProps, DisplayProps } from "../interfaces"; import { useRef, useEffect, useCallback, CSSProperties, useState } from "react"; import { useLayout } from ".."; +import { useResponsiveClasses } from "../hooks/useResponsiveClasses"; interface ClientFlexProps extends FlexProps, StyleProps, DisplayProps { cursor?: StyleProps["cursor"]; @@ -20,6 +21,8 @@ const ClientFlex = forwardRef(({ cursor, hide, const elementRef = useRef(null); const [isTouchDevice, setIsTouchDevice] = useState(false); const { currentBreakpoint } = useLayout(); + + useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); // Combine refs const combinedRef = (node: HTMLDivElement) => { diff --git a/packages/core/src/components/ClientGrid.tsx b/packages/core/src/components/ClientGrid.tsx index f1faefd..82c0f29 100644 --- a/packages/core/src/components/ClientGrid.tsx +++ b/packages/core/src/components/ClientGrid.tsx @@ -5,6 +5,7 @@ import { ServerGrid, Cursor } from "."; import { GridProps, StyleProps, DisplayProps } from "../interfaces"; import { useRef, useEffect, useCallback, CSSProperties, useState } from "react"; import { useLayout } from ".."; +import {useResponsiveClasses} from "../hooks/useResponsiveClasses"; interface ClientGridProps extends GridProps, StyleProps, DisplayProps { cursor?: StyleProps["cursor"]; @@ -20,6 +21,8 @@ const ClientGrid = forwardRef(({ cursor, hide, const elementRef = useRef(null); const [isTouchDevice, setIsTouchDevice] = useState(false); const { currentBreakpoint } = useLayout(); + + useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); // Combine refs const combinedRef = (node: HTMLDivElement) => { diff --git a/packages/core/src/hooks/useResponsiveClasses.ts b/packages/core/src/hooks/useResponsiveClasses.ts new file mode 100644 index 0000000..a5c5d0d --- /dev/null +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -0,0 +1,171 @@ +"use client"; + +import {useCallback, useEffect} from "react"; +import React, { useRef } from "react"; + +export const useResponsiveClasses = ( + elementRef: React.RefObject, + responsiveProps: { xl?: any; l?: any; m?: any; s?: any; xs?: any }, + currentBreakpoint: string, +) => { + if (!elementRef) { + return; + } + const appliedClasses = useRef>(new Set()); + + const applyResponsiveClasses = useCallback(() => { + if (!elementRef.current) return; + + const element = elementRef.current; + + // Remove all previously applied responsive classes + appliedClasses.current.forEach((className) => { + element.classList.remove(className); + }); + appliedClasses.current.clear(); + + // Helper function to get value with cascading fallback + const getValueWithCascading = (property: string) => { + const { xl, l, m, s, xs } = responsiveProps; + + switch (currentBreakpoint) { + case "xl": + return xl?.[property]; + case "l": + return l?.[property] !== undefined ? l[property] : xl?.[property]; + case "m": + return m?.[property] !== undefined + ? m[property] + : l?.[property] !== undefined + ? l[property] + : xl?.[property]; + case "s": + return s?.[property] !== undefined + ? s[property] + : m?.[property] !== undefined + ? m[property] + : l?.[property] !== undefined + ? l[property] + : xl?.[property]; + case "xs": + return xs?.[property] !== undefined + ? xs[property] + : s?.[property] !== undefined + ? s[property] + : m?.[property] !== undefined + ? m[property] + : l?.[property] !== undefined + ? l[property] + : xl?.[property]; + default: + return undefined; + } + }; + + // Properties to check for responsive classes + const properties = [ + "position", "direction", "horizontal", "vertical", + // Display properties + "overflow", "overflowX", "overflowY", + // Grid properties + "columns", + // Flex properties + "flex", "wrap", "show", "hide", + // Position offsets + "top", "right", "bottom", "left" + ]; + + properties.forEach((property) => { + const value = getValueWithCascading(property); + + if (value !== undefined) { + let className = ""; + + switch (property) { + case "position": + className = `position-${value}`; + break; + case "direction": + className = `flex-${value}`; + break; + case "horizontal": + // Determine if it should be justify or align based on direction + const direction = getValueWithCascading("direction"); + const isRowDirection = !direction || direction === "row" || direction === "row-reverse"; + className = isRowDirection + ? `justify-${value}` + : `align-${value}`; + break; + case "vertical": + // Determine if it should be justify or align based on direction + const verticalDirection = getValueWithCascading("direction"); + const isVerticalRowDirection = !verticalDirection || verticalDirection === "row" || verticalDirection === "row-reverse"; + className = isVerticalRowDirection + ? `align-${value}` + : `justify-${value}`; + break; + // Display properties + case "overflow": + className = `overflow-${value}`; + break; + case "overflowX": + className = `overflow-x-${value}`; + break; + case "overflowY": + className = `overflow-y-${value}`; + break; + // Grid properties + case "columns": + className = `columns-${value}`; + break; + // Flex properties + case "flex": + className = `flex-${value}`; + break; + case "wrap": + className = `flex-${value}`; + break; + case "show": + className = "flex-show"; + break; + case "hide": + className = "flex-hide"; + break; + // Position offsets + case "top": + className = `top-${value}`; + break; + case "right": + className = `right-${value}`; + break; + case "bottom": + className = `bottom-${value}`; + break; + case "left": + className = `left-${value}`; + break; + } + + if (className) { + element.classList.add(className); + appliedClasses.current.add(className); + } + } + }); + }, [responsiveProps, currentBreakpoint]); + + useEffect(() => { + applyResponsiveClasses(); + }, [applyResponsiveClasses]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (elementRef.current) { + appliedClasses.current.forEach((className) => { + elementRef.current?.classList.remove(className); + }); + } + }; + }, []); +}; From 0736964c64167e9eca08755ec24591c85095c217 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Fri, 8 Aug 2025 16:23:17 +0300 Subject: [PATCH 005/103] fix: add xs breakpoint support to ServerGrid for improved responsiveness --- packages/core/src/components/ServerGrid.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/core/src/components/ServerGrid.tsx b/packages/core/src/components/ServerGrid.tsx index ef65797..35d7d31 100644 --- a/packages/core/src/components/ServerGrid.tsx +++ b/packages/core/src/components/ServerGrid.tsx @@ -176,11 +176,13 @@ const ServerGrid = forwardRef( l?.position && `l-position-${l.position}`, m?.position && `m-position-${m.position}`, s?.position && `s-position-${s.position}`, + xs?.position && `xs-position-${xs.position}`, inline ? "display-inline-grid" : "display-grid", hide && "grid-hide", l?.hide && "l-grid-hide", m?.hide && "m-grid-hide", s?.hide && "s-grid-hide", + xs?.hide && "xs-grid-hide", fit && "fit", fitWidth && "fit-width", fitHeight && "fit-height", @@ -191,18 +193,22 @@ const ServerGrid = forwardRef( l?.columns && `l-columns-${l.columns}`, m?.columns && `m-columns-${m.columns}`, s?.columns && `s-columns-${s.columns}`, + xs?.columns && `xs-columns-${xs.columns}`, overflow && `overflow-${overflow}`, overflowX && `overflow-x-${overflowX}`, overflowY && `overflow-y-${overflowY}`, l?.overflow && `l-overflow-${l.overflow}`, m?.overflow && `m-overflow-${m.overflow}`, s?.overflow && `s-overflow-${s.overflow}`, + xs?.overflow && `xs-overflow-${xs.overflow}`, l?.overflowX && `l-overflow-x-${l.overflowX}`, m?.overflowX && `m-overflow-x-${m.overflowX}`, s?.overflowX && `s-overflow-x-${s.overflowX}`, + xs?.overflowX && `xs-overflow-x-${xs.overflowX}`, l?.overflowY && `l-overflow-y-${l.overflowY}`, m?.overflowY && `m-overflow-y-${m.overflowY}`, s?.overflowY && `s-overflow-y-${s.overflowY}`, + xs?.overflowY && `xs-overflow-y-${xs.overflowY}`, padding && `p-${padding}`, paddingLeft && `pl-${paddingLeft}`, paddingRight && `pr-${paddingRight}`, @@ -222,18 +228,22 @@ const ServerGrid = forwardRef( l?.top && `l-top-${l.top}`, m?.top && `m-top-${m.top}`, s?.top && `s-top-${s.top}`, + xs?.top && `xs-top-${xs.top}`, right && `right-${right}`, l?.right && `l-right-${l.right}`, m?.right && `m-right-${m.right}`, s?.right && `s-right-${s.right}`, + xs?.right && `xs-right-${xs.right}`, bottom && `bottom-${bottom}`, l?.bottom && `l-bottom-${l.bottom}`, m?.bottom && `m-bottom-${m.bottom}`, s?.bottom && `s-bottom-${s.bottom}`, + xs?.bottom && `xs-bottom-${xs.bottom}`, left && `left-${left}`, l?.left && `l-left-${l.left}`, m?.left && `m-left-${m.left}`, s?.left && `s-left-${s.left}`, + xs?.left && `xs-left-${xs.left}`, generateDynamicClass("background", background), generateDynamicClass("solid", solid), generateDynamicClass( From 3f5d12bbc7dc1ed4e52e907edb050a9190cc0f35 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Sun, 10 Aug 2025 14:54:21 +0300 Subject: [PATCH 006/103] fix: update breakpoints to fixed pixel values and enhance responsive class handling --- packages/core/src/components/ClientFlex.tsx | 7 +- packages/core/src/components/ClientGrid.tsx | 7 +- packages/core/src/components/Grid.tsx | 13 ++- packages/core/src/components/ServerFlex.tsx | 97 ++++++++++--------- packages/core/src/components/ServerGrid.tsx | 89 +++++++++-------- packages/core/src/contexts/LayoutProvider.tsx | 17 ++-- packages/core/src/styles/breakpoints.scss | 8 +- packages/core/src/styles/global.scss | 12 +-- 8 files changed, 139 insertions(+), 111 deletions(-) diff --git a/packages/core/src/components/ClientFlex.tsx b/packages/core/src/components/ClientFlex.tsx index fbf2cd7..4101180 100644 --- a/packages/core/src/components/ClientFlex.tsx +++ b/packages/core/src/components/ClientFlex.tsx @@ -20,9 +20,11 @@ interface ClientFlexProps extends FlexProps, StyleProps, DisplayProps { const ClientFlex = forwardRef(({ cursor, hide, xl, l, m, s, xs, ...props }, ref) => { const elementRef = useRef(null); const [isTouchDevice, setIsTouchDevice] = useState(false); - const { currentBreakpoint } = useLayout(); + const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); - useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); + if (!isDefaultBreakpoints) { + useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); + } // Combine refs const combinedRef = (node: HTMLDivElement) => { @@ -174,6 +176,7 @@ const ClientFlex = forwardRef(({ cursor, hide, m={m} s={s} xs={xs} + isDefaultBreakpoints={isDefaultBreakpoints} hide={effectiveHide} ref={combinedRef} style={{ diff --git a/packages/core/src/components/ClientGrid.tsx b/packages/core/src/components/ClientGrid.tsx index 82c0f29..1f5c065 100644 --- a/packages/core/src/components/ClientGrid.tsx +++ b/packages/core/src/components/ClientGrid.tsx @@ -20,9 +20,11 @@ interface ClientGridProps extends GridProps, StyleProps, DisplayProps { const ClientGrid = forwardRef(({ cursor, hide, xl, l, m, s, xs, ...props }, ref) => { const elementRef = useRef(null); const [isTouchDevice, setIsTouchDevice] = useState(false); - const { currentBreakpoint } = useLayout(); + const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); - useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); + if (!isDefaultBreakpoints) { + useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); + } // Combine refs const combinedRef = (node: HTMLDivElement) => { @@ -174,6 +176,7 @@ const ClientGrid = forwardRef(({ cursor, hide, m={m} s={s} xs={xs} + isDefaultBreakpoints={isDefaultBreakpoints} hide={effectiveHide} ref={combinedRef} style={{ diff --git a/packages/core/src/components/Grid.tsx b/packages/core/src/components/Grid.tsx index 9417bcf..c021738 100644 --- a/packages/core/src/components/Grid.tsx +++ b/packages/core/src/components/Grid.tsx @@ -66,10 +66,15 @@ const Grid = forwardRef(({ cursor, xl, l, m, s, // Check xs breakpoint if (currentBreakpoint === 'xs') { - // For xs, we cascade down from all larger breakpoints - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; + // If xs.hide is explicitly set, use that value + if (xs?.hide !== undefined) return xs.hide === true; + // Otherwise check if s.hide is set (cascading down) + if (s?.hide !== undefined) return s.hide === true; + // Otherwise check if m.hide is set (cascading down) + if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) + if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop return hide === true; } diff --git a/packages/core/src/components/ServerFlex.tsx b/packages/core/src/components/ServerFlex.tsx index f72df66..e368f48 100644 --- a/packages/core/src/components/ServerFlex.tsx +++ b/packages/core/src/components/ServerFlex.tsx @@ -23,6 +23,7 @@ interface ComponentProps m?: any; s?: any; xs?: any; + isDefaultBreakpoints?: boolean; } const ServerFlex = forwardRef( @@ -39,6 +40,7 @@ const ServerFlex = forwardRef( m, s, xs, + isDefaultBreakpoints = true, wrap = false, horizontal, vertical, @@ -173,18 +175,10 @@ const ServerFlex = forwardRef( return `${scheme}-${type}-${weight}`; }; - const classes = classNames( + let classes = classNames( inline ? "display-inline-flex" : "display-flex", position && `position-${position}`, - l?.position && `l-position-${l.position}`, - m?.position && `m-position-${m.position}`, - s?.position && `s-position-${s.position}`, - xs?.position && `xs-position-${xs.position}`, hide && "flex-hide", - l?.hide && "l-flex-hide", - m?.hide && "m-flex-hide", - s?.hide && "s-flex-hide", - xs?.hide && "xs-flex-hide", padding && `p-${padding}`, paddingLeft && `pl-${paddingLeft}`, paddingRight && `pr-${paddingRight}`, @@ -237,10 +231,6 @@ const ServerFlex = forwardRef( bottomLeftRadius && `radius-${bottomLeftRadius}-bottom-left`, bottomRightRadius && `radius-${bottomRightRadius}-bottom-right`, direction && `flex-${direction}`, - l?.direction && `l-flex-${l.direction}`, - m?.direction && `m-flex-${m.direction}`, - s?.direction && `s-flex-${s.direction}`, - xs?.direction && `xs-flex-${xs.direction}`, pointerEvents && `pointer-events-${pointerEvents}`, transition && `transition-${transition}`, opacity && `opacity-${opacity}`, @@ -257,38 +247,6 @@ const ServerFlex = forwardRef( (direction === "row" || direction === "row-reverse" || direction === undefined ? `align-${vertical}` : `justify-${vertical}`), - l?.horizontal && - (l?.direction === "row" || l?.direction === "row-reverse" || l?.direction === undefined - ? `l-justify-${l.horizontal}` - : `l-align-${l.horizontal}`), - l?.vertical && - (l?.direction === "row" || l?.direction === "row-reverse" || l?.direction === undefined - ? `l-align-${l.vertical}` - : `l-justify-${l.vertical}`), - m?.horizontal && - (m?.direction === "row" || m?.direction === "row-reverse" || m?.direction === undefined - ? `m-justify-${m.horizontal}` - : `m-align-${m.horizontal}`), - m?.vertical && - (m?.direction === "row" || m?.direction === "row-reverse" || m?.direction === undefined - ? `m-align-${m.vertical}` - : `m-justify-${m.vertical}`), - s?.horizontal && - (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined - ? `s-justify-${s.horizontal}` - : `s-align-${s.horizontal}`), - s?.vertical && - (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined - ? `s-align-${s.vertical}` - : `s-justify-${s.vertical}`), - xs?.horizontal && - (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined - ? `xs-justify-${xs.horizontal}` - : `xs-align-${xs.horizontal}`), - xs?.vertical && - (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined - ? `xs-align-${xs.vertical}` - : `xs-justify-${xs.vertical}`), center && "center", fit && "fit", fitWidth && "fit-width", @@ -311,6 +269,55 @@ const ServerFlex = forwardRef( ...variantClasses, ); + if (isDefaultBreakpoints) { + classes += " " + classNames( + l?.position && `l-position-${l.position}`, + m?.position && `m-position-${m.position}`, + s?.position && `s-position-${s.position}`, + xs?.position && `xs-position-${xs.position}`, + l?.hide && "l-flex-hide", + m?.hide && "m-flex-hide", + s?.hide && "s-flex-hide", + xs?.hide && "xs-flex-hide", + l?.direction && `l-flex-${l.direction}`, + m?.direction && `m-flex-${m.direction}`, + s?.direction && `s-flex-${s.direction}`, + xs?.direction && `xs-flex-${xs.direction}`, + l?.horizontal && + (l?.direction === "row" || l?.direction === "row-reverse" || l?.direction === undefined + ? `l-justify-${l.horizontal}` + : `l-align-${l.horizontal}`), + l?.vertical && + (l?.direction === "row" || l?.direction === "row-reverse" || l?.direction === undefined + ? `l-align-${l.vertical}` + : `l-justify-${l.vertical}`), + m?.horizontal && + (m?.direction === "row" || m?.direction === "row-reverse" || m?.direction === undefined + ? `m-justify-${m.horizontal}` + : `m-align-${m.horizontal}`), + m?.vertical && + (m?.direction === "row" || m?.direction === "row-reverse" || m?.direction === undefined + ? `m-align-${m.vertical}` + : `m-justify-${m.vertical}`), + s?.horizontal && + (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined + ? `s-justify-${s.horizontal}` + : `s-align-${s.horizontal}`), + s?.vertical && + (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined + ? `s-align-${s.vertical}` + : `s-justify-${s.vertical}`), + xs?.horizontal && + (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined + ? `xs-justify-${xs.horizontal}` + : `xs-align-${xs.horizontal}`), + xs?.vertical && + (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined + ? `xs-align-${xs.vertical}` + : `xs-justify-${xs.vertical}`) + ) + } + const parseDimension = ( value: number | SpacingToken | undefined, type: "width" | "height", diff --git a/packages/core/src/components/ServerGrid.tsx b/packages/core/src/components/ServerGrid.tsx index 35d7d31..fd5059f 100644 --- a/packages/core/src/components/ServerGrid.tsx +++ b/packages/core/src/components/ServerGrid.tsx @@ -23,6 +23,7 @@ interface ComponentProps m?: any; s?: any; xs?: any; + isDefaultBreakpoints?: boolean; } const ServerGrid = forwardRef( @@ -38,6 +39,7 @@ const ServerGrid = forwardRef( m, s, xs, + isDefaultBreakpoints = true, hide, aspectRatio, align, @@ -171,18 +173,10 @@ const ServerGrid = forwardRef( return undefined; }; - const classes = classNames( + let classes = classNames( position && `position-${position}`, - l?.position && `l-position-${l.position}`, - m?.position && `m-position-${m.position}`, - s?.position && `s-position-${s.position}`, - xs?.position && `xs-position-${xs.position}`, inline ? "display-inline-grid" : "display-grid", hide && "grid-hide", - l?.hide && "l-grid-hide", - m?.hide && "m-grid-hide", - s?.hide && "s-grid-hide", - xs?.hide && "xs-grid-hide", fit && "fit", fitWidth && "fit-width", fitHeight && "fit-height", @@ -190,25 +184,9 @@ const ServerGrid = forwardRef( (fillWidth || maxWidth) && "fill-width", (fillHeight || maxHeight) && "fill-height", columns && `columns-${columns}`, - l?.columns && `l-columns-${l.columns}`, - m?.columns && `m-columns-${m.columns}`, - s?.columns && `s-columns-${s.columns}`, - xs?.columns && `xs-columns-${xs.columns}`, overflow && `overflow-${overflow}`, overflowX && `overflow-x-${overflowX}`, overflowY && `overflow-y-${overflowY}`, - l?.overflow && `l-overflow-${l.overflow}`, - m?.overflow && `m-overflow-${m.overflow}`, - s?.overflow && `s-overflow-${s.overflow}`, - xs?.overflow && `xs-overflow-${xs.overflow}`, - l?.overflowX && `l-overflow-x-${l.overflowX}`, - m?.overflowX && `m-overflow-x-${m.overflowX}`, - s?.overflowX && `s-overflow-x-${s.overflowX}`, - xs?.overflowX && `xs-overflow-x-${xs.overflowX}`, - l?.overflowY && `l-overflow-y-${l.overflowY}`, - m?.overflowY && `m-overflow-y-${m.overflowY}`, - s?.overflowY && `s-overflow-y-${s.overflowY}`, - xs?.overflowY && `xs-overflow-y-${xs.overflowY}`, padding && `p-${padding}`, paddingLeft && `pl-${paddingLeft}`, paddingRight && `pr-${paddingRight}`, @@ -225,25 +203,9 @@ const ServerGrid = forwardRef( marginY && `my-${marginY}`, gap && `g-${gap}`, top && `top-${top}`, - l?.top && `l-top-${l.top}`, - m?.top && `m-top-${m.top}`, - s?.top && `s-top-${s.top}`, - xs?.top && `xs-top-${xs.top}`, right && `right-${right}`, - l?.right && `l-right-${l.right}`, - m?.right && `m-right-${m.right}`, - s?.right && `s-right-${s.right}`, - xs?.right && `xs-right-${xs.right}`, bottom && `bottom-${bottom}`, - l?.bottom && `l-bottom-${l.bottom}`, - m?.bottom && `m-bottom-${m.bottom}`, - s?.bottom && `s-bottom-${s.bottom}`, - xs?.bottom && `xs-bottom-${xs.bottom}`, left && `left-${left}`, - l?.left && `l-left-${l.left}`, - m?.left && `m-left-${m.left}`, - s?.left && `s-left-${s.left}`, - xs?.left && `xs-left-${xs.left}`, generateDynamicClass("background", background), generateDynamicClass("solid", solid), generateDynamicClass( @@ -283,6 +245,51 @@ const ServerGrid = forwardRef( className, ); + if (isDefaultBreakpoints) { + classes += " " + classNames( + l?.position && `l-position-${l.position}`, + m?.position && `m-position-${m.position}`, + s?.position && `s-position-${s.position}`, + xs?.position && `xs-position-${xs.position}`, + l?.hide && "l-grid-hide", + m?.hide && "m-grid-hide", + s?.hide && "s-grid-hide", + xs?.hide && "xs-grid-hide", + l?.columns && `l-columns-${l.columns}`, + m?.columns && `m-columns-${m.columns}`, + s?.columns && `s-columns-${s.columns}`, + xs?.columns && `xs-columns-${xs.columns}`, + l?.overflow && `l-overflow-${l.overflow}`, + m?.overflow && `m-overflow-${m.overflow}`, + s?.overflow && `s-overflow-${s.overflow}`, + xs?.overflow && `xs-overflow-${xs.overflow}`, + l?.overflowX && `l-overflow-x-${l.overflowX}`, + m?.overflowX && `m-overflow-x-${m.overflowX}`, + s?.overflowX && `s-overflow-x-${s.overflowX}`, + xs?.overflowX && `xs-overflow-x-${xs.overflowX}`, + l?.overflowY && `l-overflow-y-${l.overflowY}`, + m?.overflowY && `m-overflow-y-${m.overflowY}`, + s?.overflowY && `s-overflow-y-${s.overflowY}`, + xs?.overflowY && `xs-overflow-y-${xs.overflowY}`, + l?.top && `l-top-${l.top}`, + m?.top && `m-top-${m.top}`, + s?.top && `s-top-${s.top}`, + xs?.top && `xs-top-${xs.top}`, + l?.right && `l-right-${l.right}`, + m?.right && `m-right-${m.right}`, + s?.right && `s-right-${s.right}`, + xs?.right && `xs-right-${xs.right}`, + l?.bottom && `l-bottom-${l.bottom}`, + m?.bottom && `m-bottom-${m.bottom}`, + s?.bottom && `s-bottom-${s.bottom}`, + xs?.bottom && `xs-bottom-${xs.bottom}`, + l?.left && `l-left-${l.left}`, + m?.left && `m-left-${m.left}`, + s?.left && `s-left-${s.left}`, + xs?.left && `xs-left-${xs.left}`, + ) + } + const combinedStyle: CSSProperties = { maxWidth: parseDimension(maxWidth, "width"), minWidth: parseDimension(minWidth, "width"), diff --git a/packages/core/src/contexts/LayoutProvider.tsx b/packages/core/src/contexts/LayoutProvider.tsx index a1b2763..7b10569 100644 --- a/packages/core/src/contexts/LayoutProvider.tsx +++ b/packages/core/src/contexts/LayoutProvider.tsx @@ -18,6 +18,7 @@ interface LayoutContextType { currentBreakpoint: BreakpointKey; width: number; breakpoints: Breakpoints; + isDefaultBreakpoints: boolean; isBreakpoint: (key: BreakpointKey) => boolean; maxWidth: (key: BreakpointKey) => boolean; minWidth: (key: BreakpointKey) => boolean; @@ -68,13 +69,14 @@ const LayoutProvider: React.FC = ({ }; useEffect(() => { - // Update CSS custom properties - const root = document.documentElement; - Object.entries(breakpoints).forEach(([key, value]) => { - if (value !== Infinity) { - root.style.setProperty(`--breakpoint-${key}`, `${value}px`); - } - }); + // Update CSS custom properties (Not usable because of media queries) + // This part is commented out because CSS custom properties cannot be used with media queries in this + //const root = document.documentElement; + //Object.entries(breakpoints).forEach(([key, value]) => { + // if (value !== Infinity) { + // root.style.setProperty(`--breakpoint-${key}`, `${value}px`); + // } + //}); // Initialize width const updateWidth = () => { @@ -98,6 +100,7 @@ const LayoutProvider: React.FC = ({ currentBreakpoint, width, breakpoints, + isDefaultBreakpoints: JSON.stringify(breakpoints) === JSON.stringify(DEFAULT_BREAKPOINTS), isBreakpoint, maxWidth, minWidth, diff --git a/packages/core/src/styles/breakpoints.scss b/packages/core/src/styles/breakpoints.scss index a583d5c..cd70899 100644 --- a/packages/core/src/styles/breakpoints.scss +++ b/packages/core/src/styles/breakpoints.scss @@ -1,7 +1,7 @@ -$breakpoint-xs: var(--breakpoint-xs); -$breakpoint-s: var(--breakpoint-s); -$breakpoint-m: var(--breakpoint-m); -$breakpoint-l: var(--breakpoint-l); +$breakpoint-xs: 480px; +$breakpoint-s: 768px; +$breakpoint-m: 1024px; +$breakpoint-l: 1440px; @mixin xs { @media (max-width: #{$breakpoint-xs}) { diff --git a/packages/core/src/styles/global.scss b/packages/core/src/styles/global.scss index 59dfeed..d6f4935 100644 --- a/packages/core/src/styles/global.scss +++ b/packages/core/src/styles/global.scss @@ -11,12 +11,12 @@ img { user-select: none; } -:root { - --breakpoint-xs: 480px; - --breakpoint-s: 768px; - --breakpoint-m: 1024px; - --breakpoint-l: 1440px; -} +/*:root { + --breakpoint-xs: 480px; + --breakpoint-s: 768px; + --breakpoint-m: 1024px; + --breakpoint-l: 1440px; + } */ /* SELECTION */ ::selection { From 9d9c53c11cccc1465c41c6a4999ac8969269d768 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Sun, 10 Aug 2025 15:13:33 +0300 Subject: [PATCH 007/103] fix: update isDefaultBreakpoints to a function and fix hide --- packages/core/src/components/ClientFlex.tsx | 4 ++-- packages/core/src/components/ClientGrid.tsx | 4 ++-- packages/core/src/contexts/LayoutProvider.tsx | 8 ++++++-- packages/core/src/hooks/useResponsiveClasses.ts | 5 +---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/ClientFlex.tsx b/packages/core/src/components/ClientFlex.tsx index 4101180..bacab89 100644 --- a/packages/core/src/components/ClientFlex.tsx +++ b/packages/core/src/components/ClientFlex.tsx @@ -22,7 +22,7 @@ const ClientFlex = forwardRef(({ cursor, hide, const [isTouchDevice, setIsTouchDevice] = useState(false); const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); - if (!isDefaultBreakpoints) { + if (!isDefaultBreakpoints()) { useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); } @@ -176,7 +176,7 @@ const ClientFlex = forwardRef(({ cursor, hide, m={m} s={s} xs={xs} - isDefaultBreakpoints={isDefaultBreakpoints} + isDefaultBreakpoints={isDefaultBreakpoints()} hide={effectiveHide} ref={combinedRef} style={{ diff --git a/packages/core/src/components/ClientGrid.tsx b/packages/core/src/components/ClientGrid.tsx index 1f5c065..c276e56 100644 --- a/packages/core/src/components/ClientGrid.tsx +++ b/packages/core/src/components/ClientGrid.tsx @@ -22,7 +22,7 @@ const ClientGrid = forwardRef(({ cursor, hide, const [isTouchDevice, setIsTouchDevice] = useState(false); const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); - if (!isDefaultBreakpoints) { + if (!isDefaultBreakpoints()) { useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); } @@ -176,7 +176,7 @@ const ClientGrid = forwardRef(({ cursor, hide, m={m} s={s} xs={xs} - isDefaultBreakpoints={isDefaultBreakpoints} + isDefaultBreakpoints={isDefaultBreakpoints()} hide={effectiveHide} ref={combinedRef} style={{ diff --git a/packages/core/src/contexts/LayoutProvider.tsx b/packages/core/src/contexts/LayoutProvider.tsx index 7b10569..49b69af 100644 --- a/packages/core/src/contexts/LayoutProvider.tsx +++ b/packages/core/src/contexts/LayoutProvider.tsx @@ -18,7 +18,7 @@ interface LayoutContextType { currentBreakpoint: BreakpointKey; width: number; breakpoints: Breakpoints; - isDefaultBreakpoints: boolean; + isDefaultBreakpoints: () => boolean; isBreakpoint: (key: BreakpointKey) => boolean; maxWidth: (key: BreakpointKey) => boolean; minWidth: (key: BreakpointKey) => boolean; @@ -68,6 +68,10 @@ const LayoutProvider: React.FC = ({ return width > breakpoints[key]; }; + const isDefaultBreakpoints = (): boolean => { + return JSON.stringify(breakpoints) === JSON.stringify(DEFAULT_BREAKPOINTS); + } + useEffect(() => { // Update CSS custom properties (Not usable because of media queries) // This part is commented out because CSS custom properties cannot be used with media queries in this @@ -100,7 +104,7 @@ const LayoutProvider: React.FC = ({ currentBreakpoint, width, breakpoints, - isDefaultBreakpoints: JSON.stringify(breakpoints) === JSON.stringify(DEFAULT_BREAKPOINTS), + isDefaultBreakpoints, isBreakpoint, maxWidth, minWidth, diff --git a/packages/core/src/hooks/useResponsiveClasses.ts b/packages/core/src/hooks/useResponsiveClasses.ts index a5c5d0d..cb23761 100644 --- a/packages/core/src/hooks/useResponsiveClasses.ts +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -125,11 +125,8 @@ export const useResponsiveClasses = ( case "wrap": className = `flex-${value}`; break; - case "show": - className = "flex-show"; - break; case "hide": - className = "flex-hide"; + className = value ? "flex-hide" : "flex-show"; break; // Position offsets case "top": From 0e5f6c9de25724fc097e3acb023863f668a4df41 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Mon, 11 Aug 2025 13:33:19 +0300 Subject: [PATCH 008/103] code formating --- packages/core/src/components/ClientFlex.tsx | 326 ++++++++--------- packages/core/src/components/ClientGrid.tsx | 328 +++++++++--------- packages/core/src/components/Flex.tsx | 236 +++++++------ packages/core/src/components/Grid.tsx | 220 +++++++----- packages/core/src/components/ServerFlex.tsx | 110 +++--- packages/core/src/components/ServerGrid.tsx | 108 +++--- packages/core/src/contexts/LayoutProvider.tsx | 90 +++-- .../core/src/hooks/useResponsiveClasses.ts | 308 ++++++++-------- 8 files changed, 910 insertions(+), 816 deletions(-) diff --git a/packages/core/src/components/ClientFlex.tsx b/packages/core/src/components/ClientFlex.tsx index bacab89..6f0a298 100644 --- a/packages/core/src/components/ClientFlex.tsx +++ b/packages/core/src/components/ClientFlex.tsx @@ -17,179 +17,181 @@ interface ClientFlexProps extends FlexProps, StyleProps, DisplayProps { hide?: boolean; } -const ClientFlex = forwardRef(({ cursor, hide, xl, l, m, s, xs, ...props }, ref) => { - const elementRef = useRef(null); - const [isTouchDevice, setIsTouchDevice] = useState(false); - const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); +const ClientFlex = forwardRef( + ({ cursor, hide, xl, l, m, s, xs, ...props }, ref) => { + const elementRef = useRef(null); + const [isTouchDevice, setIsTouchDevice] = useState(false); + const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); if (!isDefaultBreakpoints()) { - useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); + useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); } - - // Combine refs - const combinedRef = (node: HTMLDivElement) => { - elementRef.current = node; - if (typeof ref === 'function') { - ref(node); - } else if (ref) { - ref.current = node; - } - }; - const appliedResponsiveStyles = useRef>(new Set()); - const baseStyleRef = useRef({}); - - // Responsive styles logic (client-side only) - const applyResponsiveStyles = useCallback(() => { - if (!elementRef.current) return; - - const element = elementRef.current; - - // Update base styles when style prop changes - if (props.style) { - baseStyleRef.current = { ...props.style }; - } - - // Determine which responsive props to apply based on current breakpoint - let currentResponsiveProps: any = null; - if (currentBreakpoint === 'xl' && xl) { - currentResponsiveProps = xl; - } else if (currentBreakpoint === 'l' && l) { - currentResponsiveProps = l; - } else if (currentBreakpoint === 'm' && m) { - currentResponsiveProps = m; - } else if (currentBreakpoint === 's' && s) { - currentResponsiveProps = s; - } else if (currentBreakpoint === 'xs' && xs) { - currentResponsiveProps = xs; - } - - // Clear only responsive styles, not base styles - appliedResponsiveStyles.current.forEach(key => { - (element.style as any)[key] = ''; - }); - appliedResponsiveStyles.current.clear(); - - // Reapply base styles - if (baseStyleRef.current) { - Object.entries(baseStyleRef.current).forEach(([key, value]) => { - (element.style as any)[key] = value; - }); - } - - // Apply new responsive styles if we have them for current breakpoint - if (currentResponsiveProps) { - if (currentResponsiveProps.style) { - Object.entries(currentResponsiveProps.style).forEach(([key, value]) => { - (element.style as any)[key] = value; - appliedResponsiveStyles.current.add(key); - }); - } - if (currentResponsiveProps.aspectRatio) { - element.style.aspectRatio = currentResponsiveProps.aspectRatio; - appliedResponsiveStyles.current.add('aspect-ratio'); + + // Combine refs + const combinedRef = (node: HTMLDivElement) => { + elementRef.current = node; + if (typeof ref === "function") { + ref(node); + } else if (ref) { + ref.current = node; } - } - }, [xl, l, m, s, xs, props.style, currentBreakpoint]); - - useEffect(() => { - applyResponsiveStyles(); - }, [applyResponsiveStyles]); - - // Detect touch device - useEffect(() => { - const checkTouchDevice = () => { - const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const hasPointer = window.matchMedia('(pointer: fine)').matches; - setIsTouchDevice(hasTouch && !hasPointer); }; + const appliedResponsiveStyles = useRef>(new Set()); + const baseStyleRef = useRef({}); - checkTouchDevice(); - - const mediaQuery = window.matchMedia('(pointer: fine)'); - const handlePointerChange = () => checkTouchDevice(); - - mediaQuery.addEventListener('change', handlePointerChange); - - return () => { - mediaQuery.removeEventListener('change', handlePointerChange); - }; - }, []); - - // Determine if we should hide the default cursor - const shouldHideCursor = typeof cursor === 'object' && cursor && !isTouchDevice; - - // Determine if we should apply the hide class based on current breakpoint - const shouldApplyHideClass = () => { - try { - const { currentBreakpoint } = useLayout(); - - // Logic matching the shouldHide function in Flex component - if (currentBreakpoint === 'xl') { - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; + // Responsive styles logic (client-side only) + const applyResponsiveStyles = useCallback(() => { + if (!elementRef.current) return; + + const element = elementRef.current; + + // Update base styles when style prop changes + if (props.style) { + baseStyleRef.current = { ...props.style }; } - - if (currentBreakpoint === 'l') { - if (l?.hide !== undefined) return l.hide === true; - return hide === true; + + // Determine which responsive props to apply based on current breakpoint + let currentResponsiveProps: any = null; + if (currentBreakpoint === "xl" && xl) { + currentResponsiveProps = xl; + } else if (currentBreakpoint === "l" && l) { + currentResponsiveProps = l; + } else if (currentBreakpoint === "m" && m) { + currentResponsiveProps = m; + } else if (currentBreakpoint === "s" && s) { + currentResponsiveProps = s; + } else if (currentBreakpoint === "xs" && xs) { + currentResponsiveProps = xs; } - - if (currentBreakpoint === 'm') { - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; + + // Clear only responsive styles, not base styles + appliedResponsiveStyles.current.forEach((key) => { + (element.style as any)[key] = ""; + }); + appliedResponsiveStyles.current.clear(); + + // Reapply base styles + if (baseStyleRef.current) { + Object.entries(baseStyleRef.current).forEach(([key, value]) => { + (element.style as any)[key] = value; + }); } - - if (currentBreakpoint === 's') { - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; + + // Apply new responsive styles if we have them for current breakpoint + if (currentResponsiveProps) { + if (currentResponsiveProps.style) { + Object.entries(currentResponsiveProps.style).forEach(([key, value]) => { + (element.style as any)[key] = value; + appliedResponsiveStyles.current.add(key); + }); + } + if (currentResponsiveProps.aspectRatio) { + element.style.aspectRatio = currentResponsiveProps.aspectRatio; + appliedResponsiveStyles.current.add("aspect-ratio"); + } } - - if (currentBreakpoint === 'xs') { - if (xs?.hide !== undefined) return xs.hide === true; - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; + }, [xl, l, m, s, xs, props.style, currentBreakpoint]); + + useEffect(() => { + applyResponsiveStyles(); + }, [applyResponsiveStyles]); + + // Detect touch device + useEffect(() => { + const checkTouchDevice = () => { + const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0; + const hasPointer = window.matchMedia("(pointer: fine)").matches; + setIsTouchDevice(hasTouch && !hasPointer); + }; + + checkTouchDevice(); + + const mediaQuery = window.matchMedia("(pointer: fine)"); + const handlePointerChange = () => checkTouchDevice(); + + mediaQuery.addEventListener("change", handlePointerChange); + + return () => { + mediaQuery.removeEventListener("change", handlePointerChange); + }; + }, []); + + // Determine if we should hide the default cursor + const shouldHideCursor = typeof cursor === "object" && cursor && !isTouchDevice; + + // Determine if we should apply the hide class based on current breakpoint + const shouldApplyHideClass = () => { + try { + const { currentBreakpoint } = useLayout(); + + // Logic matching the shouldHide function in Flex component + if (currentBreakpoint === "xl") { + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + if (currentBreakpoint === "l") { + if (l?.hide !== undefined) return l.hide === true; + return hide === true; + } + + if (currentBreakpoint === "m") { + if (m?.hide !== undefined) return m.hide === true; + if (l?.hide !== undefined) return l.hide === true; + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + if (currentBreakpoint === "s") { + if (s?.hide !== undefined) return s.hide === true; + if (m?.hide !== undefined) return m.hide === true; + if (l?.hide !== undefined) return l.hide === true; + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + if (currentBreakpoint === "xs") { + if (xs?.hide !== undefined) return xs.hide === true; + if (s?.hide !== undefined) return s.hide === true; + if (m?.hide !== undefined) return m.hide === true; + if (l?.hide !== undefined) return l.hide === true; + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + return hide === true; + } catch { return hide === true; } - - return hide === true; - } catch { - return hide === true; - } - }; - - // Apply hide class only if it should be hidden at current breakpoint - const effectiveHide = shouldApplyHideClass(); - - return ( - <> - - {typeof cursor === 'object' && cursor && !isTouchDevice && ( - - )} - - ); -}); + }; + + // Apply hide class only if it should be hidden at current breakpoint + const effectiveHide = shouldApplyHideClass(); + + return ( + <> + + {typeof cursor === "object" && cursor && !isTouchDevice && ( + + )} + + ); + }, +); ClientFlex.displayName = "ClientFlex"; -export { ClientFlex }; \ No newline at end of file +export { ClientFlex }; diff --git a/packages/core/src/components/ClientGrid.tsx b/packages/core/src/components/ClientGrid.tsx index c276e56..4043b12 100644 --- a/packages/core/src/components/ClientGrid.tsx +++ b/packages/core/src/components/ClientGrid.tsx @@ -5,7 +5,7 @@ import { ServerGrid, Cursor } from "."; import { GridProps, StyleProps, DisplayProps } from "../interfaces"; import { useRef, useEffect, useCallback, CSSProperties, useState } from "react"; import { useLayout } from ".."; -import {useResponsiveClasses} from "../hooks/useResponsiveClasses"; +import { useResponsiveClasses } from "../hooks/useResponsiveClasses"; interface ClientGridProps extends GridProps, StyleProps, DisplayProps { cursor?: StyleProps["cursor"]; @@ -17,179 +17,181 @@ interface ClientGridProps extends GridProps, StyleProps, DisplayProps { hide?: boolean; } -const ClientGrid = forwardRef(({ cursor, hide, xl, l, m, s, xs, ...props }, ref) => { - const elementRef = useRef(null); - const [isTouchDevice, setIsTouchDevice] = useState(false); - const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); +const ClientGrid = forwardRef( + ({ cursor, hide, xl, l, m, s, xs, ...props }, ref) => { + const elementRef = useRef(null); + const [isTouchDevice, setIsTouchDevice] = useState(false); + const { currentBreakpoint, isDefaultBreakpoints } = useLayout(); - if (!isDefaultBreakpoints()) { + if (!isDefaultBreakpoints()) { useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); - } - - // Combine refs - const combinedRef = (node: HTMLDivElement) => { - elementRef.current = node; - if (typeof ref === 'function') { - ref(node); - } else if (ref) { - ref.current = node; } - }; - const appliedResponsiveStyles = useRef>(new Set()); - const baseStyleRef = useRef({}); - - // Responsive styles logic (client-side only) - const applyResponsiveStyles = useCallback(() => { - if (!elementRef.current) return; - - const element = elementRef.current; - - // Update base styles when style prop changes - if (props.style) { - baseStyleRef.current = { ...props.style }; - } - - // Determine which responsive props to apply based on current breakpoint - let currentResponsiveProps: any = null; - if (currentBreakpoint === 'xl' && xl) { - currentResponsiveProps = xl; - } else if (currentBreakpoint === 'l' && l) { - currentResponsiveProps = l; - } else if (currentBreakpoint === 'm' && m) { - currentResponsiveProps = m; - } else if (currentBreakpoint === 's' && s) { - currentResponsiveProps = s; - } else if (currentBreakpoint === 'xs' && xs) { - currentResponsiveProps = xs; - } - - // Clear only responsive styles, not base styles - appliedResponsiveStyles.current.forEach(key => { - (element.style as any)[key] = ''; - }); - appliedResponsiveStyles.current.clear(); - - // Reapply base styles - if (baseStyleRef.current) { - Object.entries(baseStyleRef.current).forEach(([key, value]) => { - (element.style as any)[key] = value; - }); - } - - // Apply new responsive styles if we have them for current breakpoint - if (currentResponsiveProps) { - if (currentResponsiveProps.style) { - Object.entries(currentResponsiveProps.style).forEach(([key, value]) => { - (element.style as any)[key] = value; - appliedResponsiveStyles.current.add(key); - }); - } - if (currentResponsiveProps.aspectRatio) { - element.style.aspectRatio = currentResponsiveProps.aspectRatio; - appliedResponsiveStyles.current.add('aspect-ratio'); + + // Combine refs + const combinedRef = (node: HTMLDivElement) => { + elementRef.current = node; + if (typeof ref === "function") { + ref(node); + } else if (ref) { + ref.current = node; } - } - }, [xl, l, m, s, xs, props.style, currentBreakpoint]); - - useEffect(() => { - applyResponsiveStyles(); - }, [applyResponsiveStyles]); - - // Detect touch device - useEffect(() => { - const checkTouchDevice = () => { - const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; - const hasPointer = window.matchMedia('(pointer: fine)').matches; - setIsTouchDevice(hasTouch && !hasPointer); }; + const appliedResponsiveStyles = useRef>(new Set()); + const baseStyleRef = useRef({}); - checkTouchDevice(); - - const mediaQuery = window.matchMedia('(pointer: fine)'); - const handlePointerChange = () => checkTouchDevice(); - - mediaQuery.addEventListener('change', handlePointerChange); - - return () => { - mediaQuery.removeEventListener('change', handlePointerChange); - }; - }, []); - - // Determine if we should hide the default cursor - const shouldHideCursor = typeof cursor === 'object' && cursor && !isTouchDevice; - - // Determine if we should apply the hide class based on current breakpoint - const shouldApplyHideClass = () => { - try { - const { currentBreakpoint } = useLayout(); - - // Logic matching the shouldHide function in Grid component - if (currentBreakpoint === 'xl') { - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; + // Responsive styles logic (client-side only) + const applyResponsiveStyles = useCallback(() => { + if (!elementRef.current) return; + + const element = elementRef.current; + + // Update base styles when style prop changes + if (props.style) { + baseStyleRef.current = { ...props.style }; } - - if (currentBreakpoint === 'l') { - if (l?.hide !== undefined) return l.hide === true; - return hide === true; + + // Determine which responsive props to apply based on current breakpoint + let currentResponsiveProps: any = null; + if (currentBreakpoint === "xl" && xl) { + currentResponsiveProps = xl; + } else if (currentBreakpoint === "l" && l) { + currentResponsiveProps = l; + } else if (currentBreakpoint === "m" && m) { + currentResponsiveProps = m; + } else if (currentBreakpoint === "s" && s) { + currentResponsiveProps = s; + } else if (currentBreakpoint === "xs" && xs) { + currentResponsiveProps = xs; } - - if (currentBreakpoint === 'm') { - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; + + // Clear only responsive styles, not base styles + appliedResponsiveStyles.current.forEach((key) => { + (element.style as any)[key] = ""; + }); + appliedResponsiveStyles.current.clear(); + + // Reapply base styles + if (baseStyleRef.current) { + Object.entries(baseStyleRef.current).forEach(([key, value]) => { + (element.style as any)[key] = value; + }); } - - if (currentBreakpoint === 's') { - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; + + // Apply new responsive styles if we have them for current breakpoint + if (currentResponsiveProps) { + if (currentResponsiveProps.style) { + Object.entries(currentResponsiveProps.style).forEach(([key, value]) => { + (element.style as any)[key] = value; + appliedResponsiveStyles.current.add(key); + }); + } + if (currentResponsiveProps.aspectRatio) { + element.style.aspectRatio = currentResponsiveProps.aspectRatio; + appliedResponsiveStyles.current.add("aspect-ratio"); + } } - - if (currentBreakpoint === 'xs') { - if (xs?.hide !== undefined) return xs.hide === true; - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; + }, [xl, l, m, s, xs, props.style, currentBreakpoint]); + + useEffect(() => { + applyResponsiveStyles(); + }, [applyResponsiveStyles]); + + // Detect touch device + useEffect(() => { + const checkTouchDevice = () => { + const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0; + const hasPointer = window.matchMedia("(pointer: fine)").matches; + setIsTouchDevice(hasTouch && !hasPointer); + }; + + checkTouchDevice(); + + const mediaQuery = window.matchMedia("(pointer: fine)"); + const handlePointerChange = () => checkTouchDevice(); + + mediaQuery.addEventListener("change", handlePointerChange); + + return () => { + mediaQuery.removeEventListener("change", handlePointerChange); + }; + }, []); + + // Determine if we should hide the default cursor + const shouldHideCursor = typeof cursor === "object" && cursor && !isTouchDevice; + + // Determine if we should apply the hide class based on current breakpoint + const shouldApplyHideClass = () => { + try { + const { currentBreakpoint } = useLayout(); + + // Logic matching the shouldHide function in Grid component + if (currentBreakpoint === "xl") { + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + if (currentBreakpoint === "l") { + if (l?.hide !== undefined) return l.hide === true; + return hide === true; + } + + if (currentBreakpoint === "m") { + if (m?.hide !== undefined) return m.hide === true; + if (l?.hide !== undefined) return l.hide === true; + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + if (currentBreakpoint === "s") { + if (s?.hide !== undefined) return s.hide === true; + if (m?.hide !== undefined) return m.hide === true; + if (l?.hide !== undefined) return l.hide === true; + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + if (currentBreakpoint === "xs") { + if (xs?.hide !== undefined) return xs.hide === true; + if (s?.hide !== undefined) return s.hide === true; + if (m?.hide !== undefined) return m.hide === true; + if (l?.hide !== undefined) return l.hide === true; + if (xl?.hide !== undefined) return xl.hide === true; + return hide === true; + } + + return hide === true; + } catch { return hide === true; } - - return hide === true; - } catch { - return hide === true; - } - }; - - // Apply hide class only if it should be hidden at current breakpoint - const effectiveHide = shouldApplyHideClass(); - - return ( - <> - - {typeof cursor === 'object' && cursor && !isTouchDevice && ( - - )} - - ); -}); + }; + + // Apply hide class only if it should be hidden at current breakpoint + const effectiveHide = shouldApplyHideClass(); + + return ( + <> + + {typeof cursor === "object" && cursor && !isTouchDevice && ( + + )} + + ); + }, +); ClientGrid.displayName = "ClientGrid"; -export { ClientGrid }; \ No newline at end of file +export { ClientGrid }; diff --git a/packages/core/src/components/Flex.tsx b/packages/core/src/components/Flex.tsx index 67954a9..5fbdd8d 100644 --- a/packages/core/src/components/Flex.tsx +++ b/packages/core/src/components/Flex.tsx @@ -1,10 +1,23 @@ import { forwardRef } from "react"; -import { FlexProps, StyleProps, SpacingProps, SizeProps, CommonProps, DisplayProps } from "../interfaces"; +import { + FlexProps, + StyleProps, + SpacingProps, + SizeProps, + CommonProps, + DisplayProps, +} from "../interfaces"; import { useLayout } from "../contexts"; import { ClientFlex } from "./ClientFlex"; import { ServerFlex } from "./ServerFlex"; -interface SmartFlexProps extends FlexProps, StyleProps, SpacingProps, SizeProps, CommonProps, DisplayProps { +interface SmartFlexProps + extends FlexProps, + StyleProps, + SpacingProps, + SizeProps, + CommonProps, + DisplayProps { xl?: any; l?: any; m?: any; @@ -12,107 +25,132 @@ interface SmartFlexProps extends FlexProps, StyleProps, SpacingProps, SizeProps, xs?: any; } -const Flex = forwardRef(({ cursor, xl, l, m, s, xs, style, hide, ...props }, ref) => { - // Check if component should be hidden based on layout context - const shouldHide = () => { - if (!hide && !xl?.hide && !l?.hide && !m?.hide && !s?.hide && !xs?.hide) return false; - - try { - const { isBreakpoint } = useLayout(); - // Get the current breakpoint from the layout context - const currentBreakpoint = isBreakpoint('xs') ? 'xs' : - isBreakpoint('s') ? 's' : - isBreakpoint('m') ? 'm' : - isBreakpoint('l') ? 'l' : 'xl'; - - // Max-width CSS-like behavior: check from largest to smallest breakpoint - // Only apply hiding when hide is explicitly true - - // Check xl breakpoint - if (currentBreakpoint === 'xl') { - // For xl, we only apply the default hide prop - return hide === true; - } - - // Check large breakpoint - if (currentBreakpoint === 'l') { - // If l.hide is explicitly set, use that value - if (l?.hide !== undefined) return l.hide === true; - // Otherwise fall back to default hide prop - return hide === true; - } - - // Check medium breakpoint - if (currentBreakpoint === 'm') { - // If m.hide is explicitly set, use that value - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check small breakpoint - if (currentBreakpoint === 's') { - // If s.hide is explicitly set, use that value - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check xs breakpoint - if (currentBreakpoint === 'xs') { - // If xs.hide is explicitly set, use that value - if (xs?.hide !== undefined) return xs.hide === true; - // Otherwise check if s.hide is set (cascading down) - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop +const Flex = forwardRef( + ({ cursor, xl, l, m, s, xs, style, hide, ...props }, ref) => { + // Check if component should be hidden based on layout context + const shouldHide = () => { + if (!hide && !xl?.hide && !l?.hide && !m?.hide && !s?.hide && !xs?.hide) return false; + + try { + const { isBreakpoint } = useLayout(); + // Get the current breakpoint from the layout context + const currentBreakpoint = isBreakpoint("xs") + ? "xs" + : isBreakpoint("s") + ? "s" + : isBreakpoint("m") + ? "m" + : isBreakpoint("l") + ? "l" + : "xl"; + + // Max-width CSS-like behavior: check from largest to smallest breakpoint + // Only apply hiding when hide is explicitly true + + // Check xl breakpoint + if (currentBreakpoint === "xl") { + // For xl, we only apply the default hide prop + return hide === true; + } + + // Check large breakpoint + if (currentBreakpoint === "l") { + // If l.hide is explicitly set, use that value + if (l?.hide !== undefined) return l.hide === true; + // Otherwise fall back to default hide prop + return hide === true; + } + + // Check medium breakpoint + if (currentBreakpoint === "m") { + // If m.hide is explicitly set, use that value + if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) + if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop + return hide === true; + } + + // Check small breakpoint + if (currentBreakpoint === "s") { + // If s.hide is explicitly set, use that value + if (s?.hide !== undefined) return s.hide === true; + // Otherwise check if m.hide is set (cascading down) + if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) + if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop + return hide === true; + } + + // Check xs breakpoint + if (currentBreakpoint === "xs") { + // If xs.hide is explicitly set, use that value + if (xs?.hide !== undefined) return xs.hide === true; + // Otherwise check if s.hide is set (cascading down) + if (s?.hide !== undefined) return s.hide === true; + // Otherwise check if m.hide is set (cascading down) + if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) + if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop + return hide === true; + } + + // Default fallback return hide === true; + } catch { + // If LayoutProvider is not available, fall back to CSS classes + return false; } - - // Default fallback - return hide === true; - } catch { - // If LayoutProvider is not available, fall back to CSS classes + }; + + // Check if we need client-side functionality + const needsClientSide = () => { + // Custom cursor requires client-side + if (typeof cursor === "object" && cursor) return true; + + // Responsive props require client-side + if (xl || l || m || s || xs) return true; + + // Dynamic styles require client-side + if ( + style && + typeof style === "object" && + Object.keys(style as Record).length > 0 + ) + return true; + return false; + }; + + // If component should be hidden, don't render it + if (shouldHide()) { + return null; } - }; - - // Check if we need client-side functionality - const needsClientSide = () => { - // Custom cursor requires client-side - if (typeof cursor === 'object' && cursor) return true; - - // Responsive props require client-side - if (xl || l || m || s || xs) return true; - - // Dynamic styles require client-side - if (style && typeof style === 'object' && Object.keys(style as Record).length > 0) return true; - - return false; - }; - - // If component should be hidden, don't render it - if (shouldHide()) { - return null; - } - - // Use client component if any client-side functionality is needed - if (needsClientSide()) { - return ; - } - - // Use server component for static content - return ; -}); + + // Use client component if any client-side functionality is needed + if (needsClientSide()) { + return ( + + ); + } + + // Use server component for static content + return ; + }, +); Flex.displayName = "Flex"; export { Flex }; diff --git a/packages/core/src/components/Grid.tsx b/packages/core/src/components/Grid.tsx index c021738..113169c 100644 --- a/packages/core/src/components/Grid.tsx +++ b/packages/core/src/components/Grid.tsx @@ -1,10 +1,23 @@ import { forwardRef } from "react"; -import { GridProps, StyleProps, SpacingProps, SizeProps, CommonProps, DisplayProps } from "../interfaces"; +import { + GridProps, + StyleProps, + SpacingProps, + SizeProps, + CommonProps, + DisplayProps, +} from "../interfaces"; import { useLayout } from "../contexts"; import { ClientGrid } from "./ClientGrid"; import { ServerGrid } from "./ServerGrid"; -interface SmartGridProps extends GridProps, StyleProps, SpacingProps, SizeProps, CommonProps, DisplayProps { +interface SmartGridProps + extends GridProps, + StyleProps, + SpacingProps, + SizeProps, + CommonProps, + DisplayProps { xl?: any; l?: any; m?: any; @@ -12,60 +25,66 @@ interface SmartGridProps extends GridProps, StyleProps, SpacingProps, SizeProps, xs?: any; } -const Grid = forwardRef(({ cursor, xl, l, m, s, xs, style, hide, ...props }, ref) => { - // Check if component should be hidden based on layout context - const shouldHide = () => { - if (!hide && !xl?.hide && !l?.hide && !m?.hide && !s?.hide && !xs?.hide) return false; - - try { - const { isBreakpoint } = useLayout(); - // Get the current breakpoint from the layout context - const currentBreakpoint = isBreakpoint('xs') ? 'xs' : - isBreakpoint('s') ? 's' : - isBreakpoint('m') ? 'm' : - isBreakpoint('l') ? 'l' : 'xl'; - - // Max-width CSS-like behavior: check from largest to smallest breakpoint - // Only apply hiding when hide is explicitly true - - // Check xl breakpoint - if (currentBreakpoint === 'xl') { - // For xl, we only apply the default hide prop - return hide === true; - } - - // Check large breakpoint - if (currentBreakpoint === 'l') { - // If l.hide is explicitly set, use that value - if (l?.hide !== undefined) return l.hide === true; - // Otherwise fall back to default hide prop - return hide === true; - } - - // Check medium breakpoint - if (currentBreakpoint === 'm') { - // If m.hide is explicitly set, use that value - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check small breakpoint - if (currentBreakpoint === 's') { - // If s.hide is explicitly set, use that value - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check xs breakpoint - if (currentBreakpoint === 'xs') { +const Grid = forwardRef( + ({ cursor, xl, l, m, s, xs, style, hide, ...props }, ref) => { + // Check if component should be hidden based on layout context + const shouldHide = () => { + if (!hide && !xl?.hide && !l?.hide && !m?.hide && !s?.hide && !xs?.hide) return false; + + try { + const { isBreakpoint } = useLayout(); + // Get the current breakpoint from the layout context + const currentBreakpoint = isBreakpoint("xs") + ? "xs" + : isBreakpoint("s") + ? "s" + : isBreakpoint("m") + ? "m" + : isBreakpoint("l") + ? "l" + : "xl"; + + // Max-width CSS-like behavior: check from largest to smallest breakpoint + // Only apply hiding when hide is explicitly true + + // Check xl breakpoint + if (currentBreakpoint === "xl") { + // For xl, we only apply the default hide prop + return hide === true; + } + + // Check large breakpoint + if (currentBreakpoint === "l") { + // If l.hide is explicitly set, use that value + if (l?.hide !== undefined) return l.hide === true; + // Otherwise fall back to default hide prop + return hide === true; + } + + // Check medium breakpoint + if (currentBreakpoint === "m") { + // If m.hide is explicitly set, use that value + if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) + if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop + return hide === true; + } + + // Check small breakpoint + if (currentBreakpoint === "s") { + // If s.hide is explicitly set, use that value + if (s?.hide !== undefined) return s.hide === true; + // Otherwise check if m.hide is set (cascading down) + if (m?.hide !== undefined) return m.hide === true; + // Otherwise check if l.hide is set (cascading down) + if (l?.hide !== undefined) return l.hide === true; + // Finally fall back to default hide prop + return hide === true; + } + + // Check xs breakpoint + if (currentBreakpoint === "xs") { // If xs.hide is explicitly set, use that value if (xs?.hide !== undefined) return xs.hide === true; // Otherwise check if s.hide is set (cascading down) @@ -75,44 +94,63 @@ const Grid = forwardRef(({ cursor, xl, l, m, s, // Otherwise check if l.hide is set (cascading down) if (l?.hide !== undefined) return l.hide === true; // Finally fall back to default hide prop + return hide === true; + } + + // Default fallback return hide === true; + } catch { + // If LayoutProvider is not available, fall back to CSS classes + return false; } - - // Default fallback - return hide === true; - } catch { - // If LayoutProvider is not available, fall back to CSS classes + }; + + // Check if we need client-side functionality + const needsClientSide = () => { + // Custom cursor requires client-side + if (typeof cursor === "object" && cursor) return true; + + // Responsive props require client-side + if (xl || l || m || s || xs) return true; + + // Dynamic styles require client-side + if ( + style && + typeof style === "object" && + Object.keys(style as Record).length > 0 + ) + return true; + return false; + }; + + // If component should be hidden, don't render it + if (shouldHide()) { + return null; + } + + // Use client component if any client-side functionality is needed + if (needsClientSide()) { + return ( + + ); } - }; - - // Check if we need client-side functionality - const needsClientSide = () => { - // Custom cursor requires client-side - if (typeof cursor === 'object' && cursor) return true; - - // Responsive props require client-side - if (xl || l || m || s || xs) return true; - - // Dynamic styles require client-side - if (style && typeof style === 'object' && Object.keys(style as Record).length > 0) return true; - - return false; - }; - - // If component should be hidden, don't render it - if (shouldHide()) { - return null; - } - - // Use client component if any client-side functionality is needed - if (needsClientSide()) { - return ; - } - - // Use server component for static content - return ; -}); + + // Use server component for static content + return ; + }, +); Grid.displayName = "Grid"; -export { Grid }; \ No newline at end of file +export { Grid }; diff --git a/packages/core/src/components/ServerFlex.tsx b/packages/core/src/components/ServerFlex.tsx index e368f48..c4c6fb4 100644 --- a/packages/core/src/components/ServerFlex.tsx +++ b/packages/core/src/components/ServerFlex.tsx @@ -18,13 +18,13 @@ interface ComponentProps StyleProps, CommonProps, DisplayProps { - xl?: any; - l?: any; - m?: any; - s?: any; - xs?: any; - isDefaultBreakpoints?: boolean; - } + xl?: any; + l?: any; + m?: any; + s?: any; + xs?: any; + isDefaultBreakpoints?: boolean; +} const ServerFlex = forwardRef( ( @@ -122,7 +122,6 @@ const ServerFlex = forwardRef( }, ref, ) => { - if (onBackground && onSolid) { console.warn( "You cannot use both 'onBackground' and 'onSolid' props simultaneously. Only one will be applied.", @@ -198,7 +197,7 @@ const ServerFlex = forwardRef( ? "g-vertical--1" : "g-horizontal--1" : gap && `g-${gap}`, - top ? `top-${top}` : (position === "sticky" ? "top-0" : undefined), + top ? `top-${top}` : position === "sticky" ? "top-0" : undefined, right && `right-${right}`, bottom && `bottom-${bottom}`, left && `left-${left}`, @@ -212,7 +211,8 @@ const ServerFlex = forwardRef( !borderStyle && "border-solid", border && !borderWidth && "border-1", - (borderTop || borderRight || borderBottom || borderLeft || borderX || borderY) && "border-reset", + (borderTop || borderRight || borderBottom || borderLeft || borderX || borderY) && + "border-reset", borderTop && "border-top-1", borderRight && "border-right-1", borderBottom && "border-bottom-1", @@ -261,7 +261,7 @@ const ServerFlex = forwardRef( shadow && `shadow-${shadow}`, zIndex && `z-index-${zIndex}`, textType && `font-${textType}`, - typeof cursor === 'string' && `cursor-${cursor}`, + typeof cursor === "string" && `cursor-${cursor}`, dark && "dark-flex", light && "light-flex", colorClass, @@ -270,52 +270,58 @@ const ServerFlex = forwardRef( ); if (isDefaultBreakpoints) { - classes += " " + classNames( - l?.position && `l-position-${l.position}`, - m?.position && `m-position-${m.position}`, - s?.position && `s-position-${s.position}`, - xs?.position && `xs-position-${xs.position}`, - l?.hide && "l-flex-hide", - m?.hide && "m-flex-hide", - s?.hide && "s-flex-hide", - xs?.hide && "xs-flex-hide", - l?.direction && `l-flex-${l.direction}`, - m?.direction && `m-flex-${m.direction}`, - s?.direction && `s-flex-${s.direction}`, - xs?.direction && `xs-flex-${xs.direction}`, - l?.horizontal && + classes += + " " + + classNames( + l?.position && `l-position-${l.position}`, + m?.position && `m-position-${m.position}`, + s?.position && `s-position-${s.position}`, + xs?.position && `xs-position-${xs.position}`, + l?.hide && "l-flex-hide", + m?.hide && "m-flex-hide", + s?.hide && "s-flex-hide", + xs?.hide && "xs-flex-hide", + l?.direction && `l-flex-${l.direction}`, + m?.direction && `m-flex-${m.direction}`, + s?.direction && `s-flex-${s.direction}`, + xs?.direction && `xs-flex-${xs.direction}`, + l?.horizontal && (l?.direction === "row" || l?.direction === "row-reverse" || l?.direction === undefined - ? `l-justify-${l.horizontal}` - : `l-align-${l.horizontal}`), - l?.vertical && + ? `l-justify-${l.horizontal}` + : `l-align-${l.horizontal}`), + l?.vertical && (l?.direction === "row" || l?.direction === "row-reverse" || l?.direction === undefined - ? `l-align-${l.vertical}` - : `l-justify-${l.vertical}`), - m?.horizontal && + ? `l-align-${l.vertical}` + : `l-justify-${l.vertical}`), + m?.horizontal && (m?.direction === "row" || m?.direction === "row-reverse" || m?.direction === undefined - ? `m-justify-${m.horizontal}` - : `m-align-${m.horizontal}`), - m?.vertical && + ? `m-justify-${m.horizontal}` + : `m-align-${m.horizontal}`), + m?.vertical && (m?.direction === "row" || m?.direction === "row-reverse" || m?.direction === undefined - ? `m-align-${m.vertical}` - : `m-justify-${m.vertical}`), - s?.horizontal && + ? `m-align-${m.vertical}` + : `m-justify-${m.vertical}`), + s?.horizontal && (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined - ? `s-justify-${s.horizontal}` - : `s-align-${s.horizontal}`), - s?.vertical && + ? `s-justify-${s.horizontal}` + : `s-align-${s.horizontal}`), + s?.vertical && (s?.direction === "row" || s?.direction === "row-reverse" || s?.direction === undefined - ? `s-align-${s.vertical}` - : `s-justify-${s.vertical}`), - xs?.horizontal && - (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined - ? `xs-justify-${xs.horizontal}` - : `xs-align-${xs.horizontal}`), - xs?.vertical && - (xs?.direction === "row" || xs?.direction === "row-reverse" || xs?.direction === undefined - ? `xs-align-${xs.vertical}` - : `xs-justify-${xs.vertical}`) - ) + ? `s-align-${s.vertical}` + : `s-justify-${s.vertical}`), + xs?.horizontal && + (xs?.direction === "row" || + xs?.direction === "row-reverse" || + xs?.direction === undefined + ? `xs-justify-${xs.horizontal}` + : `xs-align-${xs.horizontal}`), + xs?.vertical && + (xs?.direction === "row" || + xs?.direction === "row-reverse" || + xs?.direction === undefined + ? `xs-align-${xs.vertical}` + : `xs-justify-${xs.vertical}`), + ); } const parseDimension = ( @@ -363,7 +369,7 @@ const ServerFlex = forwardRef( height: parseDimension(height, "height"), aspectRatio: aspectRatio, textAlign: align, - cursor: typeof cursor === 'string' ? cursor : undefined, + cursor: typeof cursor === "string" ? cursor : undefined, ...style, }; diff --git a/packages/core/src/components/ServerGrid.tsx b/packages/core/src/components/ServerGrid.tsx index fd5059f..7d74177 100644 --- a/packages/core/src/components/ServerGrid.tsx +++ b/packages/core/src/components/ServerGrid.tsx @@ -18,13 +18,13 @@ interface ComponentProps StyleProps, CommonProps, DisplayProps { - xl?: any; - l?: any; - m?: any; - s?: any; - xs?: any; - isDefaultBreakpoints?: boolean; - } + xl?: any; + l?: any; + m?: any; + s?: any; + xs?: any; + isDefaultBreakpoints?: boolean; +} const ServerGrid = forwardRef( ( @@ -115,7 +115,6 @@ const ServerGrid = forwardRef( }, ref, ) => { - const generateDynamicClass = (type: string, value: string | "-1" | undefined) => { if (!value) return undefined; @@ -216,7 +215,8 @@ const ServerGrid = forwardRef( !borderStyle && "border-solid", border && !borderWidth && `border-1`, - (borderTop || borderRight || borderBottom || borderLeft || borderX || borderY) && "border-reset", + (borderTop || borderRight || borderBottom || borderLeft || borderX || borderY) && + "border-reset", borderTop && "border-top-1", borderRight && "border-right-1", borderBottom && "border-bottom-1", @@ -239,55 +239,57 @@ const ServerGrid = forwardRef( shadow && `shadow-${shadow}`, zIndex && `z-index-${zIndex}`, textType && `font-${textType}`, - typeof cursor === 'string' && `cursor-${cursor}`, + typeof cursor === "string" && `cursor-${cursor}`, dark && "dark-grid", light && "light-grid", className, ); if (isDefaultBreakpoints) { - classes += " " + classNames( - l?.position && `l-position-${l.position}`, - m?.position && `m-position-${m.position}`, - s?.position && `s-position-${s.position}`, - xs?.position && `xs-position-${xs.position}`, - l?.hide && "l-grid-hide", - m?.hide && "m-grid-hide", - s?.hide && "s-grid-hide", - xs?.hide && "xs-grid-hide", - l?.columns && `l-columns-${l.columns}`, - m?.columns && `m-columns-${m.columns}`, - s?.columns && `s-columns-${s.columns}`, - xs?.columns && `xs-columns-${xs.columns}`, - l?.overflow && `l-overflow-${l.overflow}`, - m?.overflow && `m-overflow-${m.overflow}`, - s?.overflow && `s-overflow-${s.overflow}`, - xs?.overflow && `xs-overflow-${xs.overflow}`, - l?.overflowX && `l-overflow-x-${l.overflowX}`, - m?.overflowX && `m-overflow-x-${m.overflowX}`, - s?.overflowX && `s-overflow-x-${s.overflowX}`, - xs?.overflowX && `xs-overflow-x-${xs.overflowX}`, - l?.overflowY && `l-overflow-y-${l.overflowY}`, - m?.overflowY && `m-overflow-y-${m.overflowY}`, - s?.overflowY && `s-overflow-y-${s.overflowY}`, - xs?.overflowY && `xs-overflow-y-${xs.overflowY}`, - l?.top && `l-top-${l.top}`, - m?.top && `m-top-${m.top}`, - s?.top && `s-top-${s.top}`, - xs?.top && `xs-top-${xs.top}`, - l?.right && `l-right-${l.right}`, - m?.right && `m-right-${m.right}`, - s?.right && `s-right-${s.right}`, - xs?.right && `xs-right-${xs.right}`, - l?.bottom && `l-bottom-${l.bottom}`, - m?.bottom && `m-bottom-${m.bottom}`, - s?.bottom && `s-bottom-${s.bottom}`, - xs?.bottom && `xs-bottom-${xs.bottom}`, - l?.left && `l-left-${l.left}`, - m?.left && `m-left-${m.left}`, - s?.left && `s-left-${s.left}`, - xs?.left && `xs-left-${xs.left}`, - ) + classes += + " " + + classNames( + l?.position && `l-position-${l.position}`, + m?.position && `m-position-${m.position}`, + s?.position && `s-position-${s.position}`, + xs?.position && `xs-position-${xs.position}`, + l?.hide && "l-grid-hide", + m?.hide && "m-grid-hide", + s?.hide && "s-grid-hide", + xs?.hide && "xs-grid-hide", + l?.columns && `l-columns-${l.columns}`, + m?.columns && `m-columns-${m.columns}`, + s?.columns && `s-columns-${s.columns}`, + xs?.columns && `xs-columns-${xs.columns}`, + l?.overflow && `l-overflow-${l.overflow}`, + m?.overflow && `m-overflow-${m.overflow}`, + s?.overflow && `s-overflow-${s.overflow}`, + xs?.overflow && `xs-overflow-${xs.overflow}`, + l?.overflowX && `l-overflow-x-${l.overflowX}`, + m?.overflowX && `m-overflow-x-${m.overflowX}`, + s?.overflowX && `s-overflow-x-${s.overflowX}`, + xs?.overflowX && `xs-overflow-x-${xs.overflowX}`, + l?.overflowY && `l-overflow-y-${l.overflowY}`, + m?.overflowY && `m-overflow-y-${m.overflowY}`, + s?.overflowY && `s-overflow-y-${s.overflowY}`, + xs?.overflowY && `xs-overflow-y-${xs.overflowY}`, + l?.top && `l-top-${l.top}`, + m?.top && `m-top-${m.top}`, + s?.top && `s-top-${s.top}`, + xs?.top && `xs-top-${xs.top}`, + l?.right && `l-right-${l.right}`, + m?.right && `m-right-${m.right}`, + s?.right && `s-right-${s.right}`, + xs?.right && `xs-right-${xs.right}`, + l?.bottom && `l-bottom-${l.bottom}`, + m?.bottom && `m-bottom-${m.bottom}`, + s?.bottom && `s-bottom-${s.bottom}`, + xs?.bottom && `xs-bottom-${xs.bottom}`, + l?.left && `l-left-${l.left}`, + m?.left && `m-left-${m.left}`, + s?.left && `s-left-${s.left}`, + xs?.left && `xs-left-${xs.left}`, + ); } const combinedStyle: CSSProperties = { @@ -300,7 +302,7 @@ const ServerGrid = forwardRef( aspectRatio: aspectRatio, textAlign: align, // Hide default cursor when using custom cursor - cursor: typeof cursor === 'string' ? cursor : undefined, + cursor: typeof cursor === "string" ? cursor : undefined, ...style, }; diff --git a/packages/core/src/contexts/LayoutProvider.tsx b/packages/core/src/contexts/LayoutProvider.tsx index 49b69af..e4334e9 100644 --- a/packages/core/src/contexts/LayoutProvider.tsx +++ b/packages/core/src/contexts/LayoutProvider.tsx @@ -4,10 +4,10 @@ import React, { createContext, useContext, useEffect, useState, ReactNode } from // Default breakpoints export const DEFAULT_BREAKPOINTS = { - xs: 480, // Extra small (mobile small) - s: 768, // Small (mobile) - m: 1024, // Medium (tablet) - l: 1440, // Large (desktop) + xs: 480, // Extra small (mobile small) + s: 768, // Small (mobile) + m: 1024, // Medium (tablet) + l: 1440, // Large (desktop) xl: Infinity, // Above all breakpoints } as const; @@ -31,9 +31,9 @@ interface LayoutProviderProps { breakpoints?: Partial; } -const LayoutProvider: React.FC = ({ - children, - breakpoints: customBreakpoints +const LayoutProvider: React.FC = ({ + children, + breakpoints: customBreakpoints, }) => { // Merge custom breakpoints with defaults const breakpoints: Breakpoints = { @@ -42,15 +42,15 @@ const LayoutProvider: React.FC = ({ }; const [width, setWidth] = useState(0); - const [currentBreakpoint, setCurrentBreakpoint] = useState('l'); + const [currentBreakpoint, setCurrentBreakpoint] = useState("l"); // Determine current breakpoint based on width const getCurrentBreakpoint = (width: number): BreakpointKey => { - if (width <= breakpoints.xs) return 'xs'; - if (width <= breakpoints.s) return 's'; - if (width <= breakpoints.m) return 'm'; - if (width <= breakpoints.l) return 'l'; - return 'xl'; + if (width <= breakpoints.xs) return "xs"; + if (width <= breakpoints.s) return "s"; + if (width <= breakpoints.m) return "m"; + if (width <= breakpoints.l) return "l"; + return "xl"; }; // Check if current breakpoint matches the given key @@ -70,35 +70,35 @@ const LayoutProvider: React.FC = ({ const isDefaultBreakpoints = (): boolean => { return JSON.stringify(breakpoints) === JSON.stringify(DEFAULT_BREAKPOINTS); - } + }; useEffect(() => { - // Update CSS custom properties (Not usable because of media queries) - // This part is commented out because CSS custom properties cannot be used with media queries in this - //const root = document.documentElement; - //Object.entries(breakpoints).forEach(([key, value]) => { - // if (value !== Infinity) { - // root.style.setProperty(`--breakpoint-${key}`, `${value}px`); - // } - //}); - - // Initialize width - const updateWidth = () => { - const newWidth = window.innerWidth; - setWidth(newWidth); - setCurrentBreakpoint(getCurrentBreakpoint(newWidth)); - }; - - // Set initial width - updateWidth(); - - // Add resize listener - window.addEventListener('resize', updateWidth); - - return () => { - window.removeEventListener('resize', updateWidth); - }; - }, [breakpoints]); + // Update CSS custom properties (Not usable because of media queries) + // This part is commented out because CSS custom properties cannot be used with media queries in this + //const root = document.documentElement; + //Object.entries(breakpoints).forEach(([key, value]) => { + // if (value !== Infinity) { + // root.style.setProperty(`--breakpoint-${key}`, `${value}px`); + // } + //}); + + // Initialize width + const updateWidth = () => { + const newWidth = window.innerWidth; + setWidth(newWidth); + setCurrentBreakpoint(getCurrentBreakpoint(newWidth)); + }; + + // Set initial width + updateWidth(); + + // Add resize listener + window.addEventListener("resize", updateWidth); + + return () => { + window.removeEventListener("resize", updateWidth); + }; + }, [breakpoints]); const value: LayoutContextType = { currentBreakpoint, @@ -110,19 +110,15 @@ const LayoutProvider: React.FC = ({ minWidth, }; - return ( - - {children} - - ); + return {children}; }; export const useLayout = (): LayoutContextType => { const context = useContext(LayoutContext); if (!context) { - throw new Error('useLayout must be used within a LayoutProvider'); + throw new Error("useLayout must be used within a LayoutProvider"); } return context; }; -export { LayoutProvider }; \ No newline at end of file +export { LayoutProvider }; diff --git a/packages/core/src/hooks/useResponsiveClasses.ts b/packages/core/src/hooks/useResponsiveClasses.ts index cb23761..f8b1d33 100644 --- a/packages/core/src/hooks/useResponsiveClasses.ts +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -1,168 +1,178 @@ "use client"; -import {useCallback, useEffect} from "react"; +import { useCallback, useEffect } from "react"; import React, { useRef } from "react"; export const useResponsiveClasses = ( - elementRef: React.RefObject, - responsiveProps: { xl?: any; l?: any; m?: any; s?: any; xs?: any }, - currentBreakpoint: string, + elementRef: React.RefObject, + responsiveProps: { xl?: any; l?: any; m?: any; s?: any; xs?: any }, + currentBreakpoint: string, ) => { - if (!elementRef) { - return; - } - const appliedClasses = useRef>(new Set()); + if (!elementRef) { + return; + } + const appliedClasses = useRef>(new Set()); - const applyResponsiveClasses = useCallback(() => { - if (!elementRef.current) return; + const applyResponsiveClasses = useCallback(() => { + if (!elementRef.current) return; - const element = elementRef.current; + const element = elementRef.current; - // Remove all previously applied responsive classes - appliedClasses.current.forEach((className) => { - element.classList.remove(className); - }); - appliedClasses.current.clear(); + // Remove all previously applied responsive classes + appliedClasses.current.forEach((className) => { + element.classList.remove(className); + }); + appliedClasses.current.clear(); - // Helper function to get value with cascading fallback - const getValueWithCascading = (property: string) => { - const { xl, l, m, s, xs } = responsiveProps; + // Helper function to get value with cascading fallback + const getValueWithCascading = (property: string) => { + const { xl, l, m, s, xs } = responsiveProps; - switch (currentBreakpoint) { - case "xl": - return xl?.[property]; - case "l": - return l?.[property] !== undefined ? l[property] : xl?.[property]; - case "m": - return m?.[property] !== undefined - ? m[property] - : l?.[property] !== undefined - ? l[property] - : xl?.[property]; - case "s": - return s?.[property] !== undefined - ? s[property] - : m?.[property] !== undefined - ? m[property] - : l?.[property] !== undefined - ? l[property] - : xl?.[property]; - case "xs": - return xs?.[property] !== undefined - ? xs[property] - : s?.[property] !== undefined - ? s[property] - : m?.[property] !== undefined - ? m[property] - : l?.[property] !== undefined - ? l[property] - : xl?.[property]; - default: - return undefined; - } - }; + switch (currentBreakpoint) { + case "xl": + return xl?.[property]; + case "l": + return l?.[property] !== undefined ? l[property] : xl?.[property]; + case "m": + return m?.[property] !== undefined + ? m[property] + : l?.[property] !== undefined + ? l[property] + : xl?.[property]; + case "s": + return s?.[property] !== undefined + ? s[property] + : m?.[property] !== undefined + ? m[property] + : l?.[property] !== undefined + ? l[property] + : xl?.[property]; + case "xs": + return xs?.[property] !== undefined + ? xs[property] + : s?.[property] !== undefined + ? s[property] + : m?.[property] !== undefined + ? m[property] + : l?.[property] !== undefined + ? l[property] + : xl?.[property]; + default: + return undefined; + } + }; - // Properties to check for responsive classes - const properties = [ - "position", "direction", "horizontal", "vertical", - // Display properties - "overflow", "overflowX", "overflowY", - // Grid properties - "columns", - // Flex properties - "flex", "wrap", "show", "hide", - // Position offsets - "top", "right", "bottom", "left" - ]; + // Properties to check for responsive classes + const properties = [ + "position", + "direction", + "horizontal", + "vertical", + // Display properties + "overflow", + "overflowX", + "overflowY", + // Grid properties + "columns", + // Flex properties + "flex", + "wrap", + "show", + "hide", + // Position offsets + "top", + "right", + "bottom", + "left", + ]; - properties.forEach((property) => { - const value = getValueWithCascading(property); + properties.forEach((property) => { + const value = getValueWithCascading(property); - if (value !== undefined) { - let className = ""; + if (value !== undefined) { + let className = ""; - switch (property) { - case "position": - className = `position-${value}`; - break; - case "direction": - className = `flex-${value}`; - break; - case "horizontal": - // Determine if it should be justify or align based on direction - const direction = getValueWithCascading("direction"); - const isRowDirection = !direction || direction === "row" || direction === "row-reverse"; - className = isRowDirection - ? `justify-${value}` - : `align-${value}`; - break; - case "vertical": - // Determine if it should be justify or align based on direction - const verticalDirection = getValueWithCascading("direction"); - const isVerticalRowDirection = !verticalDirection || verticalDirection === "row" || verticalDirection === "row-reverse"; - className = isVerticalRowDirection - ? `align-${value}` - : `justify-${value}`; - break; - // Display properties - case "overflow": - className = `overflow-${value}`; - break; - case "overflowX": - className = `overflow-x-${value}`; - break; - case "overflowY": - className = `overflow-y-${value}`; - break; - // Grid properties - case "columns": - className = `columns-${value}`; - break; - // Flex properties - case "flex": - className = `flex-${value}`; - break; - case "wrap": - className = `flex-${value}`; - break; - case "hide": - className = value ? "flex-hide" : "flex-show"; - break; - // Position offsets - case "top": - className = `top-${value}`; - break; - case "right": - className = `right-${value}`; - break; - case "bottom": - className = `bottom-${value}`; - break; - case "left": - className = `left-${value}`; - break; - } + switch (property) { + case "position": + className = `position-${value}`; + break; + case "direction": + className = `flex-${value}`; + break; + case "horizontal": + // Determine if it should be justify or align based on direction + const direction = getValueWithCascading("direction"); + const isRowDirection = !direction || direction === "row" || direction === "row-reverse"; + className = isRowDirection ? `justify-${value}` : `align-${value}`; + break; + case "vertical": + // Determine if it should be justify or align based on direction + const verticalDirection = getValueWithCascading("direction"); + const isVerticalRowDirection = + !verticalDirection || + verticalDirection === "row" || + verticalDirection === "row-reverse"; + className = isVerticalRowDirection ? `align-${value}` : `justify-${value}`; + break; + // Display properties + case "overflow": + className = `overflow-${value}`; + break; + case "overflowX": + className = `overflow-x-${value}`; + break; + case "overflowY": + className = `overflow-y-${value}`; + break; + // Grid properties + case "columns": + className = `columns-${value}`; + break; + // Flex properties + case "flex": + className = `flex-${value}`; + break; + case "wrap": + className = `flex-${value}`; + break; + case "hide": + className = value ? "flex-hide" : "flex-show"; + break; + // Position offsets + case "top": + className = `top-${value}`; + break; + case "right": + className = `right-${value}`; + break; + case "bottom": + className = `bottom-${value}`; + break; + case "left": + className = `left-${value}`; + break; + } - if (className) { - element.classList.add(className); - appliedClasses.current.add(className); - } - } - }); - }, [responsiveProps, currentBreakpoint]); + if (className) { + element.classList.add(className); + appliedClasses.current.add(className); + } + } + }); + }, [responsiveProps, currentBreakpoint]); - useEffect(() => { - applyResponsiveClasses(); - }, [applyResponsiveClasses]); + useEffect(() => { + applyResponsiveClasses(); + }, [applyResponsiveClasses]); - // Cleanup on unmount - useEffect(() => { - return () => { - if (elementRef.current) { - appliedClasses.current.forEach((className) => { - elementRef.current?.classList.remove(className); - }); - } - }; - }, []); + // Cleanup on unmount + useEffect(() => { + return () => { + if (elementRef.current) { + appliedClasses.current.forEach((className) => { + elementRef.current?.classList.remove(className); + }); + } + }; + }, []); }; From bbce1e0453bd94dc1a666b916cf2e57febec412f Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Thu, 21 Aug 2025 23:45:29 +0300 Subject: [PATCH 009/103] fix: enhance useResponsiveClasses with property class management and original class restoration --- .../core/src/hooks/useResponsiveClasses.ts | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/packages/core/src/hooks/useResponsiveClasses.ts b/packages/core/src/hooks/useResponsiveClasses.ts index f8b1d33..13811c5 100644 --- a/packages/core/src/hooks/useResponsiveClasses.ts +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -3,6 +3,64 @@ import { useCallback, useEffect } from "react"; import React, { useRef } from "react"; +// Helper function to get all possible class names for a property +const getPropertyClassNames = (property: string): string[] => { + const classNames: string[] = []; + + switch (property) { + case "position": + classNames.push("position-static", "position-relative", "position-absolute", "position-fixed", "position-sticky"); + break; + case "direction": + classNames.push("flex-row", "flex-row-reverse", "flex-column", "flex-column-reverse"); + break; + case "horizontal": + classNames.push("justify-start", "justify-end", "justify-center", "justify-between", "justify-around", "justify-evenly", "align-start", "align-end", "align-center", "align-stretch"); + break; + case "vertical": + classNames.push("justify-start", "justify-end", "justify-center", "justify-between", "justify-around", "justify-evenly", "align-start", "align-end", "align-center", "align-stretch"); + break; + case "overflow": + classNames.push("overflow-visible", "overflow-hidden", "overflow-scroll", "overflow-auto"); + break; + case "overflowX": + classNames.push("overflow-x-visible", "overflow-x-hidden", "overflow-x-scroll", "overflow-x-auto"); + break; + case "overflowY": + classNames.push("overflow-y-visible", "overflow-y-hidden", "overflow-y-scroll", "overflow-y-auto"); + break; + case "columns": + // Generate common column numbers + for (let i = 1; i <= 12; i++) { + classNames.push(`columns-${i}`); + } + break; + case "flex": + classNames.push("flex-none", "flex-auto", "flex-1", "flex-grow", "flex-shrink"); + break; + case "wrap": + classNames.push("flex-wrap", "flex-nowrap", "flex-wrap-reverse"); + break; + case "hide": + classNames.push("flex-hide", "flex-show"); + break; + case "top": + classNames.push("top-0", "top-auto", "top-full", "top-1/2", "top-1/4", "top-3/4"); + break; + case "right": + classNames.push("right-0", "right-auto", "right-full", "right-1/2", "right-1/4", "right-3/4"); + break; + case "bottom": + classNames.push("bottom-0", "bottom-auto", "bottom-full", "bottom-1/2", "bottom-1/4", "bottom-3/4"); + break; + case "left": + classNames.push("left-0", "left-auto", "left-full", "left-1/2", "left-1/4", "left-3/4"); + break; + } + + return classNames; +}; + export const useResponsiveClasses = ( elementRef: React.RefObject, responsiveProps: { xl?: any; l?: any; m?: any; s?: any; xs?: any }, @@ -12,12 +70,20 @@ export const useResponsiveClasses = ( return; } const appliedClasses = useRef>(new Set()); + const originalClasses = useRef([]); + const isInitialized = useRef(false); const applyResponsiveClasses = useCallback(() => { if (!elementRef.current) return; const element = elementRef.current; + // Store original classes on first run + if (!isInitialized.current) { + originalClasses.current = Array.from(element.classList); + isInitialized.current = true; + } + // Remove all previously applied responsive classes appliedClasses.current.forEach((className) => { element.classList.remove(className); @@ -90,6 +156,15 @@ export const useResponsiveClasses = ( const value = getValueWithCascading(property); if (value !== undefined) { + console.log(property, value) + // Remove all possible classes for this property + const possibleClasses = getPropertyClassNames(property); + possibleClasses.forEach((possibleClass) => { + if (element.classList.contains(possibleClass)) { + element.classList.remove(possibleClass); + } + }); + let className = ""; switch (property) { @@ -157,6 +232,26 @@ export const useResponsiveClasses = ( element.classList.add(className); appliedClasses.current.add(className); } + } else { + // If value is undefined, restore original classes for this property + const possibleClasses = getPropertyClassNames(property); + const originalClassesToRestore = originalClasses.current.filter(originalClass => + possibleClasses.includes(originalClass) + ); + + // First remove all possible classes for this property + possibleClasses.forEach((possibleClass) => { + if (element.classList.contains(possibleClass)) { + element.classList.remove(possibleClass); + } + }); + + // Then restore original classes for this property + originalClassesToRestore.forEach((originalClass) => { + if (!element.classList.contains(originalClass)) { + element.classList.add(originalClass); + } + }); } }); }, [responsiveProps, currentBreakpoint]); From be9a10a62cba06acc3b1ff06a440926d26243053 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Thu, 21 Aug 2025 23:56:53 +0300 Subject: [PATCH 010/103] fix: enhance useResponsiveClasses with property class management and original class restoration --- .../core/src/hooks/useResponsiveClasses.ts | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/packages/core/src/hooks/useResponsiveClasses.ts b/packages/core/src/hooks/useResponsiveClasses.ts index f8b1d33..de2a566 100644 --- a/packages/core/src/hooks/useResponsiveClasses.ts +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -3,6 +3,67 @@ import { useCallback, useEffect } from "react"; import React, { useRef } from "react"; +// Helper function to get all possible class names for a property +const getPropertyClassNames = (property: string): string[] => { + const classNames: string[] = []; + + switch (property) { + case "position": + classNames.push("position-static", "position-relative", "position-absolute", "position-fixed", "position-sticky"); + break; + case "direction": + classNames.push("flex-row", "flex-row-reverse", "flex-column", "flex-column-reverse"); + break; + case "horizontal": + classNames.push("justify-start", "justify-end", "justify-center", "justify-between", "justify-around", "justify-evenly", "align-start", "align-end", "align-center", "align-stretch"); + break; + case "vertical": + classNames.push("justify-start", "justify-end", "justify-center", "justify-between", "justify-around", "justify-evenly", "align-start", "align-end", "align-center", "align-stretch"); + break; + case "overflow": + classNames.push("overflow-visible", "overflow-hidden", "overflow-scroll", "overflow-auto"); + break; + case "overflowX": + classNames.push("overflow-x-visible", "overflow-x-hidden", "overflow-x-scroll", "overflow-x-auto"); + break; + case "overflowY": + classNames.push("overflow-y-visible", "overflow-y-hidden", "overflow-y-scroll", "overflow-y-auto"); + break; + case "columns": + // Generate common column numbers + for (let i = 1; i <= 12; i++) { + classNames.push(`columns-${i}`); + } + break; + case "flex": + // Generate common flex classes + for (let i = 1; i <= 12; i++) { + classNames.push(`flex-${i}`); + } + break; + case "wrap": + classNames.push("flex-wrap", "flex-nowrap", "flex-wrap-reverse"); + break; + case "hide": + classNames.push("flex-hide", "flex-show"); + break; + case "top": + classNames.push("top-0", "top-1", "top-2", "top-4", "top-8", "top-12", "top-16", "top-20", "top-24", "top-32", "top-40", "top-48", "top-56", "top-64", "top-80", "top-104", "top-128", "top-160"); + break; + case "right": + classNames.push("right-0", "right-1", "right-2", "right-4", "right-8", "right-12", "right-16", "right-20", "right-24", "right-32", "right-40", "right-48", "right-56", "right-64", "right-80", "right-104", "right-128", "right-160"); + break; + case "bottom": + classNames.push("bottom-0", "bottom-1", "bottom-2", "bottom-4", "bottom-8", "bottom-12", "bottom-16", "bottom-20", "bottom-24", "bottom-32", "bottom-40", "bottom-48", "bottom-56", "bottom-64", "bottom-80", "bottom-104", "bottom-128", "bottom-160"); + break; + case "left": + classNames.push("left-0", "left-1", "left-2", "left-4", "left-8", "left-12", "left-16", "left-20", "left-24", "left-32", "left-40", "left-48", "left-56", "left-64", "left-80", "left-104", "left-128", "left-160"); + break; + } + + return classNames; +}; + export const useResponsiveClasses = ( elementRef: React.RefObject, responsiveProps: { xl?: any; l?: any; m?: any; s?: any; xs?: any }, @@ -12,12 +73,20 @@ export const useResponsiveClasses = ( return; } const appliedClasses = useRef>(new Set()); + const originalClasses = useRef([]); + const isInitialized = useRef(false); const applyResponsiveClasses = useCallback(() => { if (!elementRef.current) return; const element = elementRef.current; + // Store original classes on first run + if (!isInitialized.current) { + originalClasses.current = Array.from(element.classList); + isInitialized.current = true; + } + // Remove all previously applied responsive classes appliedClasses.current.forEach((className) => { element.classList.remove(className); @@ -90,6 +159,15 @@ export const useResponsiveClasses = ( const value = getValueWithCascading(property); if (value !== undefined) { + console.log(property, value) + // Remove all possible classes for this property + const possibleClasses = getPropertyClassNames(property); + possibleClasses.forEach((possibleClass) => { + if (element.classList.contains(possibleClass)) { + element.classList.remove(possibleClass); + } + }); + let className = ""; switch (property) { @@ -157,6 +235,26 @@ export const useResponsiveClasses = ( element.classList.add(className); appliedClasses.current.add(className); } + } else { + // If value is undefined, restore original classes for this property + const possibleClasses = getPropertyClassNames(property); + const originalClassesToRestore = originalClasses.current.filter(originalClass => + possibleClasses.includes(originalClass) + ); + + // First remove all possible classes for this property + possibleClasses.forEach((possibleClass) => { + if (element.classList.contains(possibleClass)) { + element.classList.remove(possibleClass); + } + }); + + // Then restore original classes for this property + originalClassesToRestore.forEach((originalClass) => { + if (!element.classList.contains(originalClass)) { + element.classList.add(originalClass); + } + }); } }); }, [responsiveProps, currentBreakpoint]); From 86faad2c42e583884c7b7b688406f20f94346e54 Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Fri, 22 Aug 2025 00:07:46 +0300 Subject: [PATCH 011/103] fix: remove debug log from useResponsiveClasses --- packages/core/src/hooks/useResponsiveClasses.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/hooks/useResponsiveClasses.ts b/packages/core/src/hooks/useResponsiveClasses.ts index de2a566..a853051 100644 --- a/packages/core/src/hooks/useResponsiveClasses.ts +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -159,7 +159,6 @@ export const useResponsiveClasses = ( const value = getValueWithCascading(property); if (value !== undefined) { - console.log(property, value) // Remove all possible classes for this property const possibleClasses = getPropertyClassNames(property); possibleClasses.forEach((possibleClass) => { From a564b0ef5317c4ecf13507edc843f1abb58b32fb Mon Sep 17 00:00:00 2001 From: hitomihiumi Date: Fri, 22 Aug 2025 00:20:01 +0300 Subject: [PATCH 012/103] feat: add UIBenchmark component for performance testing --- apps/dev/components/UIBenchmark.tsx | 273 ++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 apps/dev/components/UIBenchmark.tsx diff --git a/apps/dev/components/UIBenchmark.tsx b/apps/dev/components/UIBenchmark.tsx new file mode 100644 index 0000000..9e05207 --- /dev/null +++ b/apps/dev/components/UIBenchmark.tsx @@ -0,0 +1,273 @@ +"use client"; + +import React, {useState, useEffect, useCallback, JSX} from "react"; +import { + Button, + Flex, + Text, + Heading, + Column, + Badge, + Input, + Card, + Spinner, + ProgressBar, + Avatar, + Chip, + IconButton, + Grid, + Row, +} from "@once-ui-system/core"; + +interface BenchmarkResult { + testName: string; + renderTime: number; + componentsCount: number; + fps?: number; +} + +interface PerformanceMetrics { + memoryUsage?: number; + renderTime: number; + componentCount: number; +} + +export const UIBenchmark: React.FC = () => { + const [isRunning, setIsRunning] = useState(false); + const [results, setResults] = useState([]); + const [currentTest, setCurrentTest] = useState(""); + const [componentsCount, setComponentsCount] = useState(100); + + // Function for measuring performance + const measurePerformance = useCallback( + (testName: string, renderFunction: () => JSX.Element, count: number): Promise => { + return new Promise((resolve) => { + const startTime = performance.now(); + let frameCount = 0; + const fpsStart = performance.now(); + + // Render components + const renderStart = performance.now(); + renderFunction(); + const renderEnd = performance.now(); + + // Measure FPS + const measureFPS = () => { + frameCount++; + if (performance.now() - fpsStart < 1000) { + requestAnimationFrame(measureFPS); + } else { + const fps = frameCount; + const endTime = performance.now(); + + resolve({ + testName, + renderTime: renderEnd - renderStart, + componentsCount: count, + fps, + }); + } + }; + + requestAnimationFrame(measureFPS); + }); + }, + [] + ); + + // Test simple components (Button, Badge, Text) + const testSimpleComponents = useCallback(() => { + const components = []; + for (let i = 0; i < componentsCount; i++) { + components.push( + + + + Text {i} + + ); + } + return
{components}
; + }, [componentsCount]); + + // Test complex components (Card with multiple elements) + const testComplexComponents = useCallback(() => { + const components = []; + for (let i = 0; i < Math.floor(componentsCount / 5); i++) { + components.push( + + + Card {i} + Description for card {i} + + + + + + + + + ); + } + return
{components}
; + }, [componentsCount]); + + // Test Grid components + const testGridComponents = useCallback(() => { + const components = []; + for (let i = 0; i < componentsCount; i++) { + components.push( + + + Grid Item {i} + + + ); + } + return {components}; + }, [componentsCount]); + + // Test interactive components + const testInteractiveComponents = useCallback(() => { + const components = []; + for (let i = 0; i < Math.floor(componentsCount / 3); i++) { + components.push( + + + + + + ); + } + return
{components}
; + }, [componentsCount]); + + // Function to run all tests + const runBenchmark = useCallback(async () => { + setIsRunning(true); + setResults([]); + + const tests = [ + { name: "Simple Components", fn: testSimpleComponents, count: componentsCount }, + { name: "Complex Components", fn: testComplexComponents, count: Math.floor(componentsCount / 5) }, + { name: "Grid Layout", fn: testGridComponents, count: componentsCount }, + { name: "Interactive Components", fn: testInteractiveComponents, count: Math.floor(componentsCount / 3) }, + ]; + + for (const test of tests) { + setCurrentTest(test.name); + + // Small pause between tests + await new Promise(resolve => setTimeout(resolve, 100)); + + try { + const result = await measurePerformance(test.name, test.fn, test.count); + setResults(prev => [...prev, result]); + } catch (error) { + console.error(`Error in test ${test.name}:`, error); + } + } + + setCurrentTest(""); + setIsRunning(false); + }, [componentsCount, measurePerformance, testSimpleComponents, testComplexComponents, testGridComponents, testInteractiveComponents]); + + // Function to get result color based on performance + const getPerformanceColor = (renderTime: number): "success-strong" | "warning-strong" | "danger-strong" => { + if (renderTime < 5) return "success-strong"; + if (renderTime < 15) return "warning-strong"; + return "danger-strong"; + }; + + return ( + + UI Kit Performance Benchmark + + + + + This benchmark tests the performance of various UI kit components + + + + setComponentsCount(Number(e.target.value))} + min="10" + max="1000" + /> + + + + {isRunning && ( + + + + Running: {currentTest || "Preparing..."} + + + )} + + + + + Test Results + + + + {results.map((result, index) => ( + + + + {result.testName} + + Components: {result.componentsCount} + + + + + + + {result.renderTime.toFixed(2)}ms + + Render Time + + + {result.fps && ( + + 55 ? "success-strong" : result.fps > 30 ? "warning-strong" : "danger-strong"}> + {result.fps}fps + + FPS + + )} + + + + ))} + + + + Result Interpretation: + + Green- Excellent performance (<5ms) + Yellow- Good performance (5-15ms) + Red- Needs optimization (>15ms) + • FPS shows animation smoothness (60fps is ideal) + + + + + + ); +}; From 19f754477ba16581797f106f57172cd66a98dcda Mon Sep 17 00:00:00 2001 From: Lorant Date: Mon, 6 Oct 2025 17:02:26 +0200 Subject: [PATCH 013/103] merge: Flex breakpoint and performance --- .npmrc | 10 +-- apps/dev/src/app/(main)/page.tsx | 10 ++- apps/dev/src/components/Providers.tsx | 2 +- packages/core/src/components/ClientFlex.tsx | 53 +----------- packages/core/src/components/ClientGrid.tsx | 53 +----------- packages/core/src/components/Flex.tsx | 85 +------------------ packages/core/src/components/Grid.tsx | 85 +------------------ packages/core/src/contexts/LayoutProvider.tsx | 24 +++++- 8 files changed, 40 insertions(+), 282 deletions(-) diff --git a/.npmrc b/.npmrc index 993ee2a..26e9efd 100644 --- a/.npmrc +++ b/.npmrc @@ -1,11 +1,5 @@ -# Use symlinks instead of junctions on Windows -symlink=true - # Disable strict peer dependencies strict-peer-dependencies=false -# Hoist all dependencies to the root node_modules -shamefully-hoist=false - -# Use node-linker=hoisted to avoid Windows symlink issues -node-linker=hoisted +# Use shamefully-hoist for better Windows compatibility +shamefully-hoist=true diff --git a/apps/dev/src/app/(main)/page.tsx b/apps/dev/src/app/(main)/page.tsx index 55225c5..41a8fc8 100644 --- a/apps/dev/src/app/(main)/page.tsx +++ b/apps/dev/src/app/(main)/page.tsx @@ -52,7 +52,9 @@ import { MasonryGrid, TagInput, Avatar, + Background, } from "@once-ui-system/core"; +import { style } from "@/resources/once-ui.config"; export default function Home() { const [selectedEmoji, setSelectedEmoji] = React.useState(""); @@ -113,10 +115,16 @@ export default function Home() { return ( + hide by default, show on l - hide by default, show on m + hide by default, show on l + hide by default, show on m + hide by default, show on m + hide by default, show on s hide by default, show on s hide by default, show on xs + hide by default, show on xs + diff --git a/apps/dev/src/components/Providers.tsx b/apps/dev/src/components/Providers.tsx index fb8480b..3bf9d53 100644 --- a/apps/dev/src/components/Providers.tsx +++ b/apps/dev/src/components/Providers.tsx @@ -5,7 +5,7 @@ import { DataThemeProvider, IconProvider, LayoutProvider, ThemeProvider, ToastPr export function Providers({ children }: { children: React.ReactNode }) { return ( - + diff --git a/packages/core/src/components/ClientFlex.tsx b/packages/core/src/components/ClientFlex.tsx index 6f0a298..19e027b 100644 --- a/packages/core/src/components/ClientFlex.tsx +++ b/packages/core/src/components/ClientFlex.tsx @@ -119,55 +119,8 @@ const ClientFlex = forwardRef( // Determine if we should hide the default cursor const shouldHideCursor = typeof cursor === "object" && cursor && !isTouchDevice; - // Determine if we should apply the hide class based on current breakpoint - const shouldApplyHideClass = () => { - try { - const { currentBreakpoint } = useLayout(); - - // Logic matching the shouldHide function in Flex component - if (currentBreakpoint === "xl") { - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - if (currentBreakpoint === "l") { - if (l?.hide !== undefined) return l.hide === true; - return hide === true; - } - - if (currentBreakpoint === "m") { - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - if (currentBreakpoint === "s") { - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - if (currentBreakpoint === "xs") { - if (xs?.hide !== undefined) return xs.hide === true; - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - return hide === true; - } catch { - return hide === true; - } - }; - - // Apply hide class only if it should be hidden at current breakpoint - const effectiveHide = shouldApplyHideClass(); - + // Pass hide prop directly to ServerFlex - it will handle responsive hiding via CSS classes + // No need for client-side logic that causes re-renders on every resize return ( <> ( s={s} xs={xs} isDefaultBreakpoints={isDefaultBreakpoints()} - hide={effectiveHide} + hide={hide} ref={combinedRef} style={{ ...props.style, diff --git a/packages/core/src/components/ClientGrid.tsx b/packages/core/src/components/ClientGrid.tsx index 4043b12..c6c7453 100644 --- a/packages/core/src/components/ClientGrid.tsx +++ b/packages/core/src/components/ClientGrid.tsx @@ -119,55 +119,8 @@ const ClientGrid = forwardRef( // Determine if we should hide the default cursor const shouldHideCursor = typeof cursor === "object" && cursor && !isTouchDevice; - // Determine if we should apply the hide class based on current breakpoint - const shouldApplyHideClass = () => { - try { - const { currentBreakpoint } = useLayout(); - - // Logic matching the shouldHide function in Grid component - if (currentBreakpoint === "xl") { - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - if (currentBreakpoint === "l") { - if (l?.hide !== undefined) return l.hide === true; - return hide === true; - } - - if (currentBreakpoint === "m") { - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - if (currentBreakpoint === "s") { - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - if (currentBreakpoint === "xs") { - if (xs?.hide !== undefined) return xs.hide === true; - if (s?.hide !== undefined) return s.hide === true; - if (m?.hide !== undefined) return m.hide === true; - if (l?.hide !== undefined) return l.hide === true; - if (xl?.hide !== undefined) return xl.hide === true; - return hide === true; - } - - return hide === true; - } catch { - return hide === true; - } - }; - - // Apply hide class only if it should be hidden at current breakpoint - const effectiveHide = shouldApplyHideClass(); - + // Pass hide prop directly to ServerGrid - it will handle responsive hiding via CSS classes + // No need for client-side logic that causes re-renders on every resize return ( <> ( s={s} xs={xs} isDefaultBreakpoints={isDefaultBreakpoints()} - hide={effectiveHide} + hide={hide} ref={combinedRef} style={{ ...props.style, diff --git a/packages/core/src/components/Flex.tsx b/packages/core/src/components/Flex.tsx index 5fbdd8d..0b080ff 100644 --- a/packages/core/src/components/Flex.tsx +++ b/packages/core/src/components/Flex.tsx @@ -7,7 +7,6 @@ import { CommonProps, DisplayProps, } from "../interfaces"; -import { useLayout } from "../contexts"; import { ClientFlex } from "./ClientFlex"; import { ServerFlex } from "./ServerFlex"; @@ -27,84 +26,6 @@ interface SmartFlexProps const Flex = forwardRef( ({ cursor, xl, l, m, s, xs, style, hide, ...props }, ref) => { - // Check if component should be hidden based on layout context - const shouldHide = () => { - if (!hide && !xl?.hide && !l?.hide && !m?.hide && !s?.hide && !xs?.hide) return false; - - try { - const { isBreakpoint } = useLayout(); - // Get the current breakpoint from the layout context - const currentBreakpoint = isBreakpoint("xs") - ? "xs" - : isBreakpoint("s") - ? "s" - : isBreakpoint("m") - ? "m" - : isBreakpoint("l") - ? "l" - : "xl"; - - // Max-width CSS-like behavior: check from largest to smallest breakpoint - // Only apply hiding when hide is explicitly true - - // Check xl breakpoint - if (currentBreakpoint === "xl") { - // For xl, we only apply the default hide prop - return hide === true; - } - - // Check large breakpoint - if (currentBreakpoint === "l") { - // If l.hide is explicitly set, use that value - if (l?.hide !== undefined) return l.hide === true; - // Otherwise fall back to default hide prop - return hide === true; - } - - // Check medium breakpoint - if (currentBreakpoint === "m") { - // If m.hide is explicitly set, use that value - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check small breakpoint - if (currentBreakpoint === "s") { - // If s.hide is explicitly set, use that value - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check xs breakpoint - if (currentBreakpoint === "xs") { - // If xs.hide is explicitly set, use that value - if (xs?.hide !== undefined) return xs.hide === true; - // Otherwise check if s.hide is set (cascading down) - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Default fallback - return hide === true; - } catch { - // If LayoutProvider is not available, fall back to CSS classes - return false; - } - }; - // Check if we need client-side functionality const needsClientSide = () => { // Custom cursor requires client-side @@ -124,11 +45,6 @@ const Flex = forwardRef( return false; }; - // If component should be hidden, don't render it - if (shouldHide()) { - return null; - } - // Use client component if any client-side functionality is needed if (needsClientSide()) { return ( @@ -148,6 +64,7 @@ const Flex = forwardRef( } // Use server component for static content + // The hide prop is handled via CSS classes in ServerFlex return ; }, ); diff --git a/packages/core/src/components/Grid.tsx b/packages/core/src/components/Grid.tsx index 113169c..e3c0315 100644 --- a/packages/core/src/components/Grid.tsx +++ b/packages/core/src/components/Grid.tsx @@ -7,7 +7,6 @@ import { CommonProps, DisplayProps, } from "../interfaces"; -import { useLayout } from "../contexts"; import { ClientGrid } from "./ClientGrid"; import { ServerGrid } from "./ServerGrid"; @@ -27,84 +26,6 @@ interface SmartGridProps const Grid = forwardRef( ({ cursor, xl, l, m, s, xs, style, hide, ...props }, ref) => { - // Check if component should be hidden based on layout context - const shouldHide = () => { - if (!hide && !xl?.hide && !l?.hide && !m?.hide && !s?.hide && !xs?.hide) return false; - - try { - const { isBreakpoint } = useLayout(); - // Get the current breakpoint from the layout context - const currentBreakpoint = isBreakpoint("xs") - ? "xs" - : isBreakpoint("s") - ? "s" - : isBreakpoint("m") - ? "m" - : isBreakpoint("l") - ? "l" - : "xl"; - - // Max-width CSS-like behavior: check from largest to smallest breakpoint - // Only apply hiding when hide is explicitly true - - // Check xl breakpoint - if (currentBreakpoint === "xl") { - // For xl, we only apply the default hide prop - return hide === true; - } - - // Check large breakpoint - if (currentBreakpoint === "l") { - // If l.hide is explicitly set, use that value - if (l?.hide !== undefined) return l.hide === true; - // Otherwise fall back to default hide prop - return hide === true; - } - - // Check medium breakpoint - if (currentBreakpoint === "m") { - // If m.hide is explicitly set, use that value - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check small breakpoint - if (currentBreakpoint === "s") { - // If s.hide is explicitly set, use that value - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Check xs breakpoint - if (currentBreakpoint === "xs") { - // If xs.hide is explicitly set, use that value - if (xs?.hide !== undefined) return xs.hide === true; - // Otherwise check if s.hide is set (cascading down) - if (s?.hide !== undefined) return s.hide === true; - // Otherwise check if m.hide is set (cascading down) - if (m?.hide !== undefined) return m.hide === true; - // Otherwise check if l.hide is set (cascading down) - if (l?.hide !== undefined) return l.hide === true; - // Finally fall back to default hide prop - return hide === true; - } - - // Default fallback - return hide === true; - } catch { - // If LayoutProvider is not available, fall back to CSS classes - return false; - } - }; - // Check if we need client-side functionality const needsClientSide = () => { // Custom cursor requires client-side @@ -124,11 +45,6 @@ const Grid = forwardRef( return false; }; - // If component should be hidden, don't render it - if (shouldHide()) { - return null; - } - // Use client component if any client-side functionality is needed if (needsClientSide()) { return ( @@ -148,6 +64,7 @@ const Grid = forwardRef( } // Use server component for static content + // The hide prop is handled via CSS classes in ServerGrid return ; }, ); diff --git a/packages/core/src/contexts/LayoutProvider.tsx b/packages/core/src/contexts/LayoutProvider.tsx index 49b1952..4b02e1b 100644 --- a/packages/core/src/contexts/LayoutProvider.tsx +++ b/packages/core/src/contexts/LayoutProvider.tsx @@ -85,18 +85,34 @@ const LayoutProvider: React.FC = ({ // Initialize width const updateWidth = () => { const newWidth = window.innerWidth; + const newBreakpoint = getCurrentBreakpoint(newWidth); + + // Only update state if breakpoint actually changed setWidth(newWidth); - setCurrentBreakpoint(getCurrentBreakpoint(newWidth)); + setCurrentBreakpoint((prev) => { + if (prev !== newBreakpoint) { + return newBreakpoint; + } + return prev; + }); + }; + + // Debounce resize handler + let timeoutId: NodeJS.Timeout; + const debouncedUpdateWidth = () => { + clearTimeout(timeoutId); + timeoutId = setTimeout(updateWidth, 100); }; // Set initial width updateWidth(); - // Add resize listener - window.addEventListener("resize", updateWidth); + // Add resize listener with debouncing + window.addEventListener("resize", debouncedUpdateWidth); return () => { - window.removeEventListener("resize", updateWidth); + clearTimeout(timeoutId); + window.removeEventListener("resize", debouncedUpdateWidth); }; }, [breakpoints]); From 58079b9d8df22d38d8aef77ed6a6db98620930f8 Mon Sep 17 00:00:00 2001 From: Lorant Date: Mon, 6 Oct 2025 17:02:45 +0200 Subject: [PATCH 014/103] ui: remove Card hover effect on mobile touch --- packages/core/src/components/Card.module.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/Card.module.scss b/packages/core/src/components/Card.module.scss index 7c7f525..853a2f8 100644 --- a/packages/core/src/components/Card.module.scss +++ b/packages/core/src/components/Card.module.scss @@ -1,5 +1,7 @@ .card { - &:hover { - background-color: var(--neutral-alpha-weak); + @media (hover: hover) { + &:hover { + background-color: var(--neutral-alpha-weak); + } } } \ No newline at end of file From 8413e9817805cc530fef5333480f9c50e108de7c Mon Sep 17 00:00:00 2001 From: Lorant Date: Mon, 6 Oct 2025 20:26:14 +0200 Subject: [PATCH 015/103] feat: improve MegaMenu with better animations and custom content --- .../src/content/once-ui/modules/megaMenu.mdx | 144 ++++++-- .../once-ui/modules/mobileMegaMenu.mdx | 4 +- packages/core/src/icons.ts | 1 + .../core/src/modules/navigation/MegaMenu.tsx | 311 ++++++++++++------ 4 files changed, 331 insertions(+), 129 deletions(-) diff --git a/apps/docs/src/content/once-ui/modules/megaMenu.mdx b/apps/docs/src/content/once-ui/modules/megaMenu.mdx index 59eabe8..1ebbdcc 100644 --- a/apps/docs/src/content/once-ui/modules/megaMenu.mdx +++ b/apps/docs/src/content/once-ui/modules/megaMenu.mdx @@ -1,11 +1,13 @@ --- title: "MegaMenu" summary: "MegaMenu displays a top-level navigation bar with expandable dropdowns for organized access to grouped links. It supports dynamic sizing and contextual visibility." -updatedAt: "2025-05-09" +updatedAt: "2025-10-06" docs: "once-ui/modules/megaMenu.mdx" github: "modules/navigation/MegaMenu.tsx" navLabel: "MegaMenu" navIcon: "modules" +navTag: "Update" +navTagVariant: "indigo" --- Use `MegaMenu` for complex navigational layouts where primary sections include sub-links, descriptions, and icons. Ideal for app headers and multi-level SaaS interfaces. @@ -55,6 +57,12 @@ Use `MegaMenu` for complex navigational layouts where primary sections include s icon: "settings", description: "Configure your preferences", }, + { + label: "Monitor", + href: "#", + icon: "screen", + description: "Monitor your metrics", + }, ], }, ], @@ -122,23 +130,6 @@ Use `MegaMenu` for complex navigational layouts where primary sections include s }, ], }, - { - title: "Support", - links: [ - { - label: "Help center", - href: "#", - icon: "help", - description: "Get your questions answered", - }, - { - label: "Community", - href: "#", - icon: "person", - description: "Connect with other users", - }, - ], - }, ], }, { @@ -358,14 +349,124 @@ Use `MegaMenu` for complex navigational layouts where primary sections include s ]} />`, language: "tsx", - label: "Megamenu" + label: "MegaMenu" + } + ]} +/> + +## Custom content + +You can use the `content` prop to render custom JSX instead of the default sections layout: + + + + + New Release + + + + + + AI Features + + Agents and Chatbots + + + + + + + Performance + + 10x faster than before + + + + + + ), + }, + { + id: "products", + label: "Products", + href: "/products", + }, + ]} + /> + + } + codes={[ + { + code: +` + + New Release + + + + + + AI Features + + Explore our new AI-powered tools + + + + + + + Performance + + 10x faster than before + + + + + + ), + }, + ]} +/>`, + language: "tsx", + label: "Custom content" } ]} /> ## Usage -The `MegaMenu` component is designed for desktop screens. You can hide it at the medium breakpoint by adding the `hide="m"` prop directly to the component. +The `MegaMenu` component is designed for desktop screens. You can hide it at the medium breakpoint by adding the `m={{hide: true}}` prop directly to the component. ## API reference @@ -380,7 +481,7 @@ The `MegaMenu` component is designed for desktop screens. You can hide it at the ### Types -```ts +```tsx interface MenuGroup { id: string; label: ReactNode; @@ -388,6 +489,7 @@ interface MenuGroup { href?: string; selected?: boolean; sections?: MenuSection[]; + content?: ReactNode; // Custom content slot for dropdown } interface MenuSection { diff --git a/apps/docs/src/content/once-ui/modules/mobileMegaMenu.mdx b/apps/docs/src/content/once-ui/modules/mobileMegaMenu.mdx index 79c1c1a..43973cd 100644 --- a/apps/docs/src/content/once-ui/modules/mobileMegaMenu.mdx +++ b/apps/docs/src/content/once-ui/modules/mobileMegaMenu.mdx @@ -365,7 +365,7 @@ Use `MobileMegaMenu` as the mobile counterpart to `MegaMenu`. It supports dynami ## Usage -The `MobileMegaMenu` component is designed for mobile screens. You can hide it at the large breakpoint by adding the `hide="l"` prop directly to the component. Make sure you design a desktop navigation that works well with your app. +The `MobileMegaMenu` component is designed for mobile screens. You can hide it at the large breakpoint by adding the `hide` boolean prop directly to the component, and overwriting it at a breakpoint using the `hide={{l: false}}` prop. Make sure you design a desktop navigation that works well with your app. ## API reference @@ -380,7 +380,7 @@ The `MobileMegaMenu` component is designed for mobile screens. You can hide it a ### Types -```ts +```tsx interface MenuGroup { id: string; label: ReactNode; diff --git a/packages/core/src/icons.ts b/packages/core/src/icons.ts index 90e4eca..f048f8f 100644 --- a/packages/core/src/icons.ts +++ b/packages/core/src/icons.ts @@ -94,6 +94,7 @@ export const iconLibrary: Record = { enter: HiOutlineArrowTurnDownLeft, play: HiOutlinePlay, pause: HiOutlinePause, + screen: HiOutlineComputerDesktop, }; export type IconLibrary = typeof iconLibrary; diff --git a/packages/core/src/modules/navigation/MegaMenu.tsx b/packages/core/src/modules/navigation/MegaMenu.tsx index 3c26b9e..29c6d2d 100644 --- a/packages/core/src/modules/navigation/MegaMenu.tsx +++ b/packages/core/src/modules/navigation/MegaMenu.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useRef, useEffect, ReactNode } from "react"; +import React, { useState, useRef, useEffect, ReactNode, useMemo, useCallback } from "react"; import { usePathname } from "next/navigation"; import { Flex, Row, Column, Text, Icon, ToggleButton } from "../../"; import styles from "./MegaMenu.module.scss"; @@ -25,6 +25,7 @@ export interface MenuGroup { href?: string; selected?: boolean; sections?: MenuSection[]; + content?: ReactNode; } export interface MegaMenuProps extends React.ComponentProps { @@ -35,12 +36,15 @@ export interface MegaMenuProps extends React.ComponentProps { export const MegaMenu: React.FC = ({ menuGroups, className, ...rest }) => { const pathname = usePathname(); const [activeDropdown, setActiveDropdown] = useState(null); - const [dropdownPosition, setDropdownPosition] = useState({ left: 0, width: 0 }); + const [dropdownPosition, setDropdownPosition] = useState({ left: 0, width: 0, height: 0 }); const [isFirstAppearance, setIsFirstAppearance] = useState(true); + const previousDropdownRef = useRef(null); const dropdownRef = useRef(null); const buttonRefs = useRef>({}); const contentRefs = useRef>({}); + const measureTimeoutRef = useRef | undefined>(undefined); + const closeTimeoutRef = useRef | undefined>(undefined); useEffect(() => { if (activeDropdown && buttonRefs.current[activeDropdown]) { @@ -49,28 +53,86 @@ export const MegaMenu: React.FC = ({ menuGroups, className, ...re const rect = buttonElement.getBoundingClientRect(); const parentRect = buttonElement.parentElement?.getBoundingClientRect() || { left: 0 }; - // Set initial position + // Set initial position immediately setDropdownPosition({ left: rect.left - parentRect.left, - width: 300, // Default width that will be updated + width: 300, + height: 200, // Default height }); - // Measure content after render + // Measure content dimensions after render - use double RAF for layout completion requestAnimationFrame(() => { - const contentElement = contentRefs.current[activeDropdown]; - if (contentElement) { - const contentWidth = contentElement.scrollWidth; - setDropdownPosition((prev) => ({ - ...prev, - width: contentWidth + 40, // Add padding - })); - } + requestAnimationFrame(() => { + if (dropdownRef.current) { + const dropdown = dropdownRef.current; + + // Find the active content row + const activeContent = contentRefs.current[activeDropdown]; + + if (activeContent) { + // Find all fillWidth buttons and temporarily override their width + const fillWidthButtons = activeContent.querySelectorAll('[class*="fill-width"]') as NodeListOf; + const originalWidths: string[] = []; + + fillWidthButtons.forEach((button, index) => { + originalWidths[index] = button.style.width; + button.style.width = 'max-content'; + }); + + // Temporarily remove constraints to measure natural size + const originalHeight = dropdown.style.height; + const originalWidth = dropdown.style.width; + const originalOverflow = dropdown.style.overflow; + + dropdown.style.height = 'auto'; + dropdown.style.width = 'max-content'; + dropdown.style.overflow = 'visible'; + + // Force reflow + dropdown.offsetHeight; + + // Measure the active content + const contentWidth = activeContent.scrollWidth; // Use scrollWidth for full content + const contentHeight = activeContent.offsetHeight; + + console.log('Content measurements:', { + activeDropdown, + contentWidth, + contentHeight, + offsetWidth: activeContent.offsetWidth, + }); + + // Restore button widths + fillWidthButtons.forEach((button, index) => { + button.style.width = originalWidths[index]; + }); + + // Restore original dimensions + dropdown.style.height = originalHeight; + dropdown.style.width = originalWidth; + dropdown.style.overflow = originalOverflow; + + // Add padding for the wrapper (12px on each side) + paddingTop (8px) + border (1px each side) + setDropdownPosition({ + left: rect.left - parentRect.left, + width: contentWidth + 26, // Add wrapper padding (24) + border (2) + height: contentHeight + 34, // Add wrapper padding (24) + paddingTop (8) + border (2) + }); + } + } + }); }); } } else { // Reset first appearance flag when dropdown is closed setIsFirstAppearance(true); } + + return () => { + if (measureTimeoutRef.current) { + clearTimeout(measureTimeoutRef.current); + } + }; }, [activeDropdown]); // Reset animation flag after animation completes @@ -90,56 +152,57 @@ export const MegaMenu: React.FC = ({ menuGroups, className, ...re }, [pathname]); // Check if a menu item should be selected based on the current path - const isSelected = (href?: string) => { + const isSelected = useCallback((href?: string) => { if (!href || !pathname) return false; return pathname.startsWith(href); - }; + }, [pathname]); - // Filter groups to only show those with sections in the dropdown - const dropdownGroups = menuGroups.filter((group) => group.sections); + // Filter groups to only show those with sections or custom content in the dropdown + const dropdownGroups = useMemo( + () => menuGroups.filter((group) => group.sections || group.content), + [menuGroups] + ); // Add click handler to close dropdown when clicking on links - const handleLinkClick = (href: string) => { + const handleLinkClick = useCallback(() => { setActiveDropdown(null); - // Let the default navigation happen - }; + }, []); return ( - + {menuGroups.map((group, index) => ( { buttonRefs.current[group.id] = el; }} - onMouseEnter={() => group.sections && setActiveDropdown(group.id)} - onMouseLeave={(e) => { - // Check if we're not hovering over the dropdown - const dropdownElement = dropdownRef.current; - if (dropdownElement) { - const rect = dropdownElement.getBoundingClientRect(); - if ( - e.clientX >= rect.left && - e.clientX <= rect.right && - e.clientY >= rect.top && - e.clientY <= rect.bottom - ) { - // We're hovering over the dropdown, don't hide it - return; - } + paddingRight="8" + onMouseEnter={() => { + // Cancel any pending close + if (closeTimeoutRef.current) { + clearTimeout(closeTimeoutRef.current); } - // Only hide if activeDropdown is this group - if (activeDropdown === group.id) { - setActiveDropdown(null); + + if (group.sections || group.content) { + // Use requestAnimationFrame to ensure this runs after any pending close + requestAnimationFrame(() => { + setActiveDropdown(group.id); + }); } }} + onMouseLeave={() => { + // Start a timer to close the dropdown + closeTimeoutRef.current = setTimeout(() => { + setActiveDropdown(null); + }, 100); + }} > {group.label} - {group.sections && group.suffixIcon && ( + {(group.sections || group.content) && group.suffixIcon && ( )} @@ -158,15 +221,22 @@ export const MegaMenu: React.FC = ({ menuGroups, className, ...re style={{ left: `${dropdownPosition.left}px`, width: `${dropdownPosition.width}px`, - transition: "left 0.3s ease, width 0.3s ease", + height: `${dropdownPosition.height}px`, + transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)", visibility: "visible", + overflow: "hidden", }} onMouseEnter={() => { - // Keep the current active dropdown when hovering over it + // Cancel the close timer if we re-enter + if (closeTimeoutRef.current) { + clearTimeout(closeTimeoutRef.current); + } }} onMouseLeave={() => { - // Hide dropdown when mouse leaves it - setActiveDropdown(null); + // Start a timer to close the dropdown + closeTimeoutRef.current = setTimeout(() => { + setActiveDropdown(null); + }, 100); }} > = ({ menuGroups, className, ...re shadow="xl" padding="12" gap="32" + data-dropdown-wrapper + style={{ + transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)", + }} > - {dropdownGroups.map( - (group, groupIndex) => - activeDropdown === group.id && - group.sections && ( - { - contentRefs.current[group.id] = el; - }} - > - {group.sections.map((section, sectionIndex) => ( - - {section.title && ( - - {section.title} - - )} - {section.links.map((link, linkIndex) => ( - handleLinkClick(link.href)} - > - {link.description ? ( - - {link.icon && ( - - )} - - - {link.label} - - {link.description} - - - ) : ( - link.label - )} - - ))} - - ))} - - ), - )} + {/* Render all dropdown contents, but only show the active one */} + {dropdownGroups.map((group, groupIndex) => { + const isActive = activeDropdown === group.id; + const wasActive = previousDropdownRef.current === group.id; + // Animate when switching between dropdowns (not when first opening or returning to same) + const shouldAnimate = isActive && !wasActive && previousDropdownRef.current !== null; + + // Update previous ref when active changes + if (isActive && !wasActive) { + previousDropdownRef.current = group.id; + } else if (!activeDropdown) { + previousDropdownRef.current = null; + } + + return ( + { + contentRefs.current[group.id] = el; + }} + style={{ + position: isActive ? "relative" : "absolute", + transform: isActive ? "scale(1)" : "scale(0.9)", + opacity: isActive ? 1 : 0, + pointerEvents: isActive ? "auto" : "none", + transition: shouldAnimate ? "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)" : "opacity 0.2s ease-in", + visibility: isActive ? "visible" : "hidden", + }} + > + {/* Render custom content if provided, otherwise render sections */} + {group.content ? ( + group.content + ) : ( + group.sections?.map((section, sectionIndex) => ( + + {section.title && ( + + {section.title} + + )} + {section.links.map((link, linkIndex) => ( + + {link.description ? ( + + {link.icon && ( + + )} + + + {link.label} + + {link.description} + + + ) : ( + link.label + )} + + ))} + + )) + )} + + ); + })} )} From 5c4df48f5403f1f7fed41c897e05bc73297534b6 Mon Sep 17 00:00:00 2001 From: Lorant Date: Mon, 6 Oct 2025 20:26:44 +0200 Subject: [PATCH 016/103] fix: CodeBlock better error handling --- .../src/content/once-ui/modules/codeBlock.mdx | 2 -- packages/core/src/modules/code/CodeBlock.tsx | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/docs/src/content/once-ui/modules/codeBlock.mdx b/apps/docs/src/content/once-ui/modules/codeBlock.mdx index 317568f..f56c62d 100644 --- a/apps/docs/src/content/once-ui/modules/codeBlock.mdx +++ b/apps/docs/src/content/once-ui/modules/codeBlock.mdx @@ -6,8 +6,6 @@ github: "modules/code/CodeBlock.tsx" docs: "once-ui/modules/codeBlock.mdx" navLabel: "CodeBlock" navIcon: "modules" -navTag: "Update" -navTagVariant: "indigo" --- The `CodeBlock` component provides a way to display code snippets with syntax highlighting, interactive tabs for multiple code examples, and optional live previews. It supports various customization options including copy buttons, fullscreen mode, and line numbers. diff --git a/packages/core/src/modules/code/CodeBlock.tsx b/packages/core/src/modules/code/CodeBlock.tsx index 9347861..572144b 100644 --- a/packages/core/src/modules/code/CodeBlock.tsx +++ b/packages/core/src/modules/code/CodeBlock.tsx @@ -150,14 +150,21 @@ const loadedLanguages = new Set(["markup", "css", "clike", "javascript"] const loadLanguageWithDependencies = async (lang: string): Promise => { if (typeof window === "undefined") return false; + // Handle language aliases + const languageAliases: Record = { + ts: "typescript", + }; + + const actualLang = languageAliases[lang] || lang; + // Skip if already loaded - if (loadedLanguages.has(lang)) { + if (loadedLanguages.has(actualLang)) { return true; } try { // Load dependencies first - const dependencies = languageDependencies[lang] || []; + const dependencies = languageDependencies[actualLang] || []; for (const dep of dependencies) { if (!loadedLanguages.has(dep)) { await loadLanguageWithDependencies(dep); @@ -165,8 +172,9 @@ const loadLanguageWithDependencies = async (lang: string): Promise => { } // Load the main language - await import(`prismjs/components/prism-${lang}`); - loadedLanguages.add(lang); + await import(`prismjs/components/prism-${actualLang}`); + loadedLanguages.add(actualLang); + loadedLanguages.add(lang); // Also mark the alias as loaded return true; } catch (error) { console.warn(`✗ Failed to load Prism language '${lang}':`, error); @@ -283,11 +291,16 @@ const renderDiff = ( // Apply syntax highlighting to code lines let highlightedLines: string[] = []; + if (lang && Prism.languages[lang]) { try { highlightedLines = codeLines.map((line) => { try { - return Prism.highlight(line.content, Prism.languages[lang], lang); + // Check if language is loaded before highlighting + if (Prism.languages[lang]) { + return Prism.highlight(line.content, Prism.languages[lang], lang); + } + return line.content; } catch (error) { console.warn(`Failed to highlight line: ${line.content}`, error); return line.content; @@ -407,6 +420,7 @@ const CodeBlock: React.FC = ({ language: "", }; const { code, language, startLineNumber } = codeInstance; + const highlight = codeInstance.highlight !== undefined ? codeInstance.highlight : deprecatedHighlight; @@ -432,7 +446,7 @@ const CodeBlock: React.FC = ({ Prism.highlightAll(); }, 0); } - }, [dependenciesLoaded, code, codes.length, selectedInstance, isFullscreen, isAnimating]); + }, [dependenciesLoaded, code, codes.length, selectedInstance, isFullscreen, isAnimating, language]); useEffect(() => { if (isFullscreen) { From c4e695cf75d40e0105787e4c3b1c829e66d0e02e Mon Sep 17 00:00:00 2001 From: Lorant Date: Mon, 6 Oct 2025 21:03:58 +0200 Subject: [PATCH 017/103] feat: character count for input and textarea --- .../content/once-ui/form-controls/input.mdx | 34 +++++++++++++++++- .../once-ui/form-controls/textarea.mdx | 35 +++++++++++++++++++ apps/docs/src/product/InputExamples.tsx | 19 ++++++++++ apps/docs/src/product/TextareaExamples.tsx | 20 +++++++++++ packages/core/src/components/Input.tsx | 20 ++++++++++- packages/core/src/components/Textarea.tsx | 18 ++++++++++ 6 files changed, 144 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/content/once-ui/form-controls/input.mdx b/apps/docs/src/content/once-ui/form-controls/input.mdx index 3ee7e30..6fb55e5 100644 --- a/apps/docs/src/content/once-ui/form-controls/input.mdx +++ b/apps/docs/src/content/once-ui/form-controls/input.mdx @@ -8,7 +8,7 @@ navLabel: "Input" navIcon: "switch" --- -import { ValidationInputExample } from "../../product/InputExample" +import { ValidationInputExample, CharacterCountExample } from "../../product/InputExample" The `Input` component provides a text entry field with floating labels, validation support, and customizable styling. Use it for collecting user input in forms. @@ -221,6 +221,37 @@ Use the `description` prop to add a description below the input. ]} /> +## Character count + +Display a character counter with color-coded feedback based on remaining characters. The counter changes color when approaching the limit. + +} + codes={[ + { + code: +`const [bio, setBio] = useState(""); + +const handleChange = (e: React.ChangeEvent) => { + setBio(e.target.value); +}; + +`, + language: "tsx", + label: "Character count" + } + ]} +/> + ## NumberInput The `NumberInput` component provides a specialized input for numeric values with increment and decrement buttons. It supports min, max, and step values to control the range and increments. @@ -401,6 +432,7 @@ You can use the `radius` prop to apply custom radius to sides or corners. It wor ["radius", ["none", "top", "right", "bottom", "left", "top-left", "top-right", "bottom-right", "bottom-left"]], ["hasPrefix", "ReactNode"], ["hasSuffix", "ReactNode"], + ["characterCount", "boolean", "false"], ["cursor", ["interactive"]], ["validate", "(value: ReactNode) => ReactNode | null"], ["className"], diff --git a/apps/docs/src/content/once-ui/form-controls/textarea.mdx b/apps/docs/src/content/once-ui/form-controls/textarea.mdx index 9160688..1359a28 100644 --- a/apps/docs/src/content/once-ui/form-controls/textarea.mdx +++ b/apps/docs/src/content/once-ui/form-controls/textarea.mdx @@ -8,6 +8,8 @@ navLabel: "Textarea" navIcon: "switch" --- +import { ValidationTextareaExample, TextareaCharacterCountExample } from "../../product/TextareaExamples" + The `Textarea` component provides a multi-line text input field for collecting longer form text content from users. It supports various features like auto-resizing, validation, and custom styling. +## Character count + +Display a character counter with color-coded feedback based on remaining characters. The counter changes color when approaching the limit. + +} + codes={[ + { + code: +`const [bio, setBio] = useState(""); + +const handleChange = (e: React.ChangeEvent) => { + setBio(e.target.value); +}; + +