UNPKG

@vitus-labs/coolgrid

Version:

Ultra flexible and extensible grid system inspired by Bootstrap grid, based on styled-components

435 lines (414 loc) 11.6 kB
import { ALIGN_CONTENT_MAP_X, Provider, context, extendCss, makeItResponsive, value } from "@vitus-labs/unistyle"; import { createContext, useContext, useMemo } from "react"; import { config, get, omit, pick, useStableValue } from "@vitus-labs/core"; import { jsx } from "react/jsx-runtime"; //#region src/constants.ts const PKG_NAME = "@vitus-labs/coolgrid"; /** * Grid configuration keys that are passed through React context * from Container to Row and from Row to Col components. */ const CONTEXT_KEYS = [ "columns", "size", "gap", "padding", "gutter", "colCss", "colComponent", "rowCss", "rowComponent", "contentAlignX" ]; //#endregion //#region src/context/ContainerContext.ts /** * React context for container-level grid configuration. * Provided by the Container component and consumed by Row children * to inherit columns, gap, gutter, and other grid settings. */ var ContainerContext_default = createContext({}); //#endregion //#region src/context/RowContext.ts /** * React context for row-level grid configuration. * Provided by the Row component and consumed by Col children * to inherit columns, gap, gutter, and sizing for width calculations. */ var RowContext_default = createContext({}); //#endregion //#region src/useContext.tsx const pickThemeProps = (props, keywords) => pick(props, keywords); const getGridContext = (props = {}, theme = {}) => ({ columns: get(props, "columns") || get(theme, "grid.columns") || get(theme, "coolgrid.columns"), containerWidth: get(props, "width") || get(theme, "grid.container") || get(theme, "coolgrid.container") }); const useGridContext = (props) => { const { theme } = useContext(context); const stableCtxProps = useStableValue(pickThemeProps(props, CONTEXT_KEYS)); return useMemo(() => { return { ...getGridContext(stableCtxProps, theme), ...stableCtxProps }; }, [stableCtxProps, theme]); }; //#endregion //#region src/utils.ts /** Checks whether a value is a finite number. */ const isNumber = (value) => Number.isFinite(value); /** Checks whether a value is a finite number greater than zero. */ const hasValue = (value) => isNumber(value) && value > 0; /** * Determines if a column should be visible. A column is visible when its * size is undefined (auto) or a non-zero number. Size 0 hides the column. */ const isVisible = (value) => isNumber(value) && value !== 0 || value === void 0; const omitCtxKeys = (props) => omit(props, CONTEXT_KEYS); //#endregion //#region src/Col/styled.ts const { styled: styled$2, css: css$2, component: component$2 } = config; /** Returns true when both size and columns are valid, enabling explicit width calculation. */ const hasWidth = (size, columns) => hasValue(size) && hasValue(columns); /** * Calculates column width as a percentage of total columns, subtracting * the gap when present. On web uses `calc(%)`, on native uses absolute pixels. */ const widthStyles = ({ size, columns, gap, RNparentWidth }, { rootSize }) => { if (!hasWidth(size, columns)) return ""; const s = size; const c = columns; const g = gap; const width = s / c * 100; const v = value(hasValue(gap) ? `calc(${width}% - ${g}px)` : `${width}%`, rootSize); return css$2` flex-grow: 0; flex-shrink: 0; ${css$2` max-width: ${v}; flex-basis: ${v}; `} `; }; /** Applies half of the given value as either margin or padding (used for gap and padding distribution). */ const spacingStyles$1 = (type, param, rootSize) => { if (!isNumber(param)) return ""; return css$2` ${`${type}: ${value(param / 2, rootSize)}`}; `; }; /** * Main responsive style block for Col. When the column is visible, applies * width, padding, margin, and extra CSS. When hidden (size === 0), moves * the element off-screen with fixed positioning. */ const styles$2 = ({ theme, css, rootSize }) => { const { size, columns, gap, padding, extraStyles, RNparentWidth } = theme; if (isVisible(size)) return css` ${"left: initial;"} position: relative; ${widthStyles({ size, columns, gap, RNparentWidth }, { rootSize })}; ${spacingStyles$1("padding", padding, rootSize)}; ${spacingStyles$1("margin", gap, rootSize)}; ${extendCss(extraStyles)}; `; return css` left: -9999px; position: ${"fixed"}; margin: 0; padding: 0; `; }; var styled_default$2 = styled$2(component$2)` ${css$2` box-sizing: border-box; justify-content: stretch; `} position: relative; display: flex; flex-basis: 0; flex-grow: 1; flex-direction: column; ${makeItResponsive({ key: "$coolgrid", styles: styles$2, css: css$2, normalize: true })}; `; //#endregion //#region src/Col/component.tsx /** * Col (column) component that reads grid settings from RowContext * (columns, gap, gutter) and calculates its own width as a fraction * of the total columns. Supports responsive size, padding, and visibility. */ const DEV_PROPS$2 = process.env.NODE_ENV !== "production" ? { "data-coolgrid": "col" } : {}; const Component$2 = ({ children, component, css, ...props }) => { const { colCss, colComponent, columns, gap, size, padding } = useGridContext({ ...useContext(RowContext_default), ...props }); const finalProps = useMemo(() => ({ $coolgrid: { columns, gap, size, padding, extraStyles: css ?? colCss } }), [ columns, gap, size, padding, css, colCss ]); return /* @__PURE__ */ jsx(styled_default$2, { ...omitCtxKeys(props), as: component ?? colComponent, ...finalProps, ...DEV_PROPS$2, children }); }; const name$2 = `${PKG_NAME}/Col`; Component$2.displayName = name$2; Component$2.pkgName = PKG_NAME; Component$2.VITUS_LABS__COMPONENT = name$2; //#endregion //#region src/Col/index.ts var Col_default = Component$2; //#endregion //#region src/Container/styled.ts const { styled: styled$1, css: css$1, component: component$1 } = config; /** Responsive styles that apply the container's max-width and any extra CSS at each breakpoint. */ const styles$1 = ({ theme: t, css, rootSize }) => { const w = t.width != null && typeof t.width !== "object" ? t.width : null; return css` ${w != null ? `max-width: ${value(w, rootSize)};` : ""}; ${extendCss(t.extraStyles)}; `; }; /** Styled Container element. Centered via auto margins with responsive max-width. */ var styled_default$1 = styled$1(component$1)` display: flex; flex-direction: column; ${css$1` box-sizing: border-box; width: 100%; margin-right: auto; margin-left: auto; `} ${makeItResponsive({ key: "$coolgrid", styles: styles$1, css: css$1, normalize: true })}; `; //#endregion //#region src/Container/component.tsx /** * Container component that establishes the outermost grid boundary. * Resolves grid config from the theme, provides it to descendant Row/Col * components via ContainerContext, and renders a styled wrapper with * responsive max-width. */ const DEV_PROPS$1 = process.env.NODE_ENV !== "production" ? { "data-coolgrid": "container" } : {}; const Component$1 = ({ children, component, css, width, ...props }) => { const { containerWidth, columns, size, gap, padding, gutter, colCss, colComponent, rowCss, rowComponent, contentAlignX } = useGridContext(props); const context = useMemo(() => ({ columns, size, gap, padding, gutter, colCss, colComponent, rowCss, rowComponent, contentAlignX }), [ columns, size, gap, padding, gutter, colCss, colComponent, rowCss, rowComponent, contentAlignX ]); const finalWidth = useMemo(() => { if (!width) return containerWidth; return typeof width === "function" ? width(containerWidth) : width; }, [width, containerWidth]); const finalProps = useMemo(() => ({ $coolgrid: { width: finalWidth, extraStyles: css } }), [finalWidth, css]); return /* @__PURE__ */ jsx(styled_default$1, { ...omitCtxKeys(props), as: component, ...finalProps, ...DEV_PROPS$1, children: /* @__PURE__ */ jsx(ContainerContext_default.Provider, { value: context, children }) }); }; const name$1 = `${PKG_NAME}/Container`; Component$1.displayName = name$1; Component$1.pkgName = PKG_NAME; Component$1.VITUS_LABS__COMPONENT = name$1; //#endregion //#region src/Container/index.ts var Container_default = Component$1; //#endregion //#region src/Row/styled.ts const { styled, css, component } = config; const spacingStyles = ({ gap, gutter }, { rootSize }) => { if (!isNumber(gap)) return ""; const g = gap; const getValue = (param) => value(param, rootSize); const spacingX = g / 2 * -1; return css` margin: ${getValue(isNumber(gutter) ? gutter - g / 2 : g / 2)} ${getValue(spacingX)}; `; }; /** Maps the contentAlignX prop to a CSS justify-content value. */ const contentAlign = (align) => { if (!align) return ""; return css` justify-content: ${ALIGN_CONTENT_MAP_X[align]}; `; }; /** Composes spacing, alignment, and extra CSS into a single responsive style block for the Row. */ const styles = ({ theme, css, rootSize }) => { const { gap, gutter, contentAlignX, extraStyles } = theme; return css` ${spacingStyles({ gap, gutter }, { rootSize })}; ${contentAlign(contentAlignX)}; ${extendCss(extraStyles)}; `; }; var styled_default = styled(component)` ${css` box-sizing: border-box; `}; display: flex; flex-wrap: wrap; align-self: stretch; flex-direction: row; ${makeItResponsive({ key: "$coolgrid", styles, css, normalize: true })}; `; //#endregion //#region src/Row/component.tsx /** * Row component that reads inherited config from ContainerContext, merges * it with its own props, and provides the resolved grid settings (columns, * gap, gutter) to Col children via RowContext. Renders a flex-wrap container * with negative margins to offset column gutters. */ const DEV_PROPS = process.env.NODE_ENV !== "production" ? { "data-coolgrid": "row" } : {}; const Component = ({ children, component, css, contentAlignX: rowAlignX, ...props }) => { const { columns, gap, gutter, rowComponent, rowCss, contentAlignX, containerWidth, size, padding, colCss, colComponent } = useGridContext({ ...useContext(ContainerContext_default), ...props }); const context = useMemo(() => ({ containerWidth, size, padding, colCss, colComponent, columns, gap, gutter }), [ containerWidth, size, padding, colCss, colComponent, columns, gap, gutter ]); const finalProps = useMemo(() => ({ $coolgrid: { contentAlignX: rowAlignX || contentAlignX, columns, gap, gutter, extraStyles: css || rowCss } }), [ rowAlignX, contentAlignX, columns, gap, gutter, css, rowCss ]); return /* @__PURE__ */ jsx(styled_default, { ...omitCtxKeys(props), as: component || rowComponent, ...finalProps, ...DEV_PROPS, children: /* @__PURE__ */ jsx(RowContext_default.Provider, { value: context, children }) }); }; const name = `${PKG_NAME}/Row`; Component.displayName = name; Component.pkgName = PKG_NAME; Component.VITUS_LABS__COMPONENT = name; //#endregion //#region src/Row/index.ts var Row_default = Component; //#endregion //#region src/theme.ts /** * Default Bootstrap-like grid configuration. Provides 5 breakpoints (xs-xl), * a 12-column grid, and responsive container max-widths matching Bootstrap 4. */ var theme_default = { rootSize: 16, breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 }, grid: { columns: 12, container: { xs: "100%", sm: 540, md: 720, lg: 960, xl: 1140 } } }; //#endregion export { Col_default as Col, Container_default as Container, Provider, Row_default as Row, theme_default as theme }; //# sourceMappingURL=index.js.map