diff --git a/packages/core/src/components/ClientFlex.tsx b/packages/core/src/components/ClientFlex.tsx index e76e139..6f0a298 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"]; @@ -16,174 +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 } = useLayout(); - - // 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; - }); +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); } - - // 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 f1faefd..4043b12 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"]; @@ -16,174 +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 } = useLayout(); - - // 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; - }); +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()) { + useResponsiveClasses(elementRef, { xl, l, m, s, xs }, currentBreakpoint); } - - // 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 0e863db..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,102 +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') { - // 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; +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 9417bcf..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,102 +25,132 @@ 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') { - // 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; +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; } - - // 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 ; + }, +); 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 2cc8037..c4c6fb4 100644 --- a/packages/core/src/components/ServerFlex.tsx +++ b/packages/core/src/components/ServerFlex.tsx @@ -18,12 +18,13 @@ interface ComponentProps StyleProps, CommonProps, DisplayProps { - xl?: any; - l?: any; - m?: any; - s?: any; - xs?: any; - } + xl?: any; + l?: any; + 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, @@ -120,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.", @@ -173,16 +174,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}`, hide && "flex-hide", - l?.hide && "l-flex-hide", - m?.hide && "m-flex-hide", - s?.hide && "s-flex-hide", padding && `p-${padding}`, paddingLeft && `pl-${paddingLeft}`, paddingRight && `pr-${paddingRight}`, @@ -202,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}`, @@ -216,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", @@ -235,9 +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}`, pointerEvents && `pointer-events-${pointerEvents}`, transition && `transition-${transition}`, opacity && `opacity-${opacity}`, @@ -254,30 +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}`), center && "center", fit && "fit", fitWidth && "fit-width", @@ -292,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, @@ -300,6 +269,61 @@ 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", @@ -345,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 ef65797..7d74177 100644 --- a/packages/core/src/components/ServerGrid.tsx +++ b/packages/core/src/components/ServerGrid.tsx @@ -18,12 +18,13 @@ interface ComponentProps StyleProps, CommonProps, DisplayProps { - xl?: any; - l?: any; - m?: any; - s?: any; - xs?: any; - } + xl?: any; + l?: any; + 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, @@ -113,7 +115,6 @@ const ServerGrid = forwardRef( }, ref, ) => { - const generateDynamicClass = (type: string, value: string | "-1" | undefined) => { if (!value) return undefined; @@ -171,16 +172,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}`, inline ? "display-inline-grid" : "display-grid", hide && "grid-hide", - l?.hide && "l-grid-hide", - m?.hide && "m-grid-hide", - s?.hide && "s-grid-hide", fit && "fit", fitWidth && "fit-width", fitHeight && "fit-height", @@ -188,21 +183,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}`, 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}`, - l?.overflowX && `l-overflow-x-${l.overflowX}`, - m?.overflowX && `m-overflow-x-${m.overflowX}`, - s?.overflowX && `s-overflow-x-${s.overflowX}`, - l?.overflowY && `l-overflow-y-${l.overflowY}`, - m?.overflowY && `m-overflow-y-${m.overflowY}`, - s?.overflowY && `s-overflow-y-${s.overflowY}`, padding && `p-${padding}`, paddingLeft && `pl-${paddingLeft}`, paddingRight && `pr-${paddingRight}`, @@ -219,21 +202,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}`, right && `right-${right}`, - l?.right && `l-right-${l.right}`, - m?.right && `m-right-${m.right}`, - s?.right && `s-right-${s.right}`, bottom && `bottom-${bottom}`, - l?.bottom && `l-bottom-${l.bottom}`, - m?.bottom && `m-bottom-${m.bottom}`, - s?.bottom && `s-bottom-${s.bottom}`, left && `left-${left}`, - l?.left && `l-left-${l.left}`, - m?.left && `m-left-${m.left}`, - s?.left && `s-left-${s.left}`, generateDynamicClass("background", background), generateDynamicClass("solid", solid), generateDynamicClass( @@ -244,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", @@ -267,12 +239,59 @@ 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}`, + ); + } + const combinedStyle: CSSProperties = { maxWidth: parseDimension(maxWidth, "width"), minWidth: parseDimension(minWidth, "width"), @@ -283,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 425b7b3..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; @@ -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; @@ -30,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 = { @@ -41,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 @@ -67,7 +68,20 @@ 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 + //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; @@ -79,10 +93,10 @@ const LayoutProvider: React.FC = ({ updateWidth(); // Add resize listener - window.addEventListener('resize', updateWidth); + window.addEventListener("resize", updateWidth); return () => { - window.removeEventListener('resize', updateWidth); + window.removeEventListener("resize", updateWidth); }; }, [breakpoints]); @@ -90,24 +104,21 @@ const LayoutProvider: React.FC = ({ currentBreakpoint, width, breakpoints, + isDefaultBreakpoints, isBreakpoint, maxWidth, 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 new file mode 100644 index 0000000..f8b1d33 --- /dev/null +++ b/packages/core/src/hooks/useResponsiveClasses.ts @@ -0,0 +1,178 @@ +"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 "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]); + + useEffect(() => { + applyResponsiveClasses(); + }, [applyResponsiveClasses]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (elementRef.current) { + appliedClasses.current.forEach((className) => { + elementRef.current?.classList.remove(className); + }); + } + }; + }, []); +}; diff --git a/packages/core/src/styles/breakpoints.scss b/packages/core/src/styles/breakpoints.scss index 1defea0..cd70899 100644 --- a/packages/core/src/styles/breakpoints.scss +++ b/packages/core/src/styles/breakpoints.scss @@ -1,7 +1,14 @@ +$breakpoint-xs: 480px; $breakpoint-s: 768px; $breakpoint-m: 1024px; $breakpoint-l: 1440px; +@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/global.scss b/packages/core/src/styles/global.scss index 52b03df..d6f4935 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); 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; }