Skip to content

Commit

Permalink
feat: add draft responsive components and utils
Browse files Browse the repository at this point in the history
  • Loading branch information
abelflopes committed Jul 24, 2024
1 parent d6d8f29 commit f4f8a10
Show file tree
Hide file tree
Showing 16 changed files with 428 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/components/responsive/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Hack for module resolution of non built packages
export * from "./src/index";
38 changes: 38 additions & 0 deletions packages/components/responsive/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@react-ck/responsive",
"private": false,
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"sass": "src/styles/shared.scss",
"files": [
"/dist",
"/src/styles"
],
"homepage": "https://github.com/abelflopes/react-ck/tree/master/packages/components/card#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/abelflopes/react-ck.git"
},
"scripts": {
"build": "NODE_ENV=production webpack",
"lint:typescript": "tsc --noEmit",
"lint:code": "eslint . --ext ts,tsx --cache",
"test": "npx -y npm-run-all -s test:*",
"test:unit": "jest --testPathPattern=\".unit.*\"",
"test:snapshot": "jest --testPathPattern=\".snapshot.*\"",
"test:snapshot:update": "jest --testPathPattern=\".snapshot.*\" -u"
},
"devDependencies": {
"@react-ck/typescript-config": "^1.0.0",
"@react-ck/webpack-config": "^1.0.0",
"@types/react": "^18.2.33"
},
"peerDependencies": {
"react": "^18.2.0"
},
"dependencies": {
"@react-ck/theme": "^1.9.0",
"classnames": "^2.3.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { type default as React, useEffect } from "react";
import { type ResponsiveTarget, type EnabledBreakpointsMapping } from "../types";
import { useResponsive } from "../hooks/responsive";

export interface ResponsiveFragmentProps extends EnabledBreakpointsMapping {
target?: ResponsiveTarget;
children?: React.ReactNode;
}

export const ResponsiveFragment = ({
target = "viewport",
children,
...enabledBreakpoints
}: Readonly<ResponsiveFragmentProps>): React.ReactNode => {
const responsive = useResponsive(target);

useEffect(() => {
// eslint-disable-next-line no-console -- draft
console.log("target", target);
}, [target]);

useEffect(() => {
// eslint-disable-next-line no-console -- draft
console.log("responsive", responsive);
}, [responsive]);

useEffect(() => {
// eslint-disable-next-line no-console -- draft
console.log("enabledBreakpoints", enabledBreakpoints);
}, [enabledBreakpoints]);

return children;
};
13 changes: 13 additions & 0 deletions packages/components/responsive/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const breakpoints = {
xs: 0,
s: 540,
m: 700,
l: 1024,
xl: 1200,
xxl: 1600,
};

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- in this case we are sure the type matches
export const breakpointKeys = Object.keys(breakpoints) as unknown as Array<
keyof typeof breakpoints
>;
14 changes: 14 additions & 0 deletions packages/components/responsive/src/hooks/responsive-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type BaseProps, type ResponsiveProps } from "../types";

export interface UseResponsiveProps<T extends BaseProps> extends ResponsiveProps<T> {
baseProps: T;
}

export const useResponsiveProps = <T extends BaseProps>({
baseProps,
responsive,
}: UseResponsiveProps<T>): T => {
// eslint-disable-next-line no-console -- draft
console.log("baseProps, responsive", baseProps, responsive);
return baseProps;
};
84 changes: 84 additions & 0 deletions packages/components/responsive/src/hooks/responsive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useEffect, useState } from "react";
import { type ResponsiveTarget, type EnabledBreakpointsMapping } from "../types";
import { breakpointKeys, breakpoints } from "../constants";

/**
* Returns a map that informs which breakpoints are active
* @param target - which reference to use, defaults to viewport but can also use an element
* @returns @see {@link UseResponsiveData}
*/
export const useResponsive = (target: ResponsiveTarget = "viewport"): EnabledBreakpointsMapping => {
const [activeBreakpoints, setActiveBreakpoints] = useState<EnabledBreakpointsMapping>({
xs: false,
s: false,
m: false,
l: false,
xl: false,
xxl: false,
});

// Handle HTMLElement detection
useEffect(() => {
if (target === "viewport") return;

const el = target.current;
if (!el) return;

const check = (): void => {
breakpointKeys.forEach((bpKey) => {
const width = el.clientWidth;

setActiveBreakpoints((v) => ({
...v,
[bpKey]: width >= breakpoints[bpKey],
}));
});
};

const resizeObserver = new ResizeObserver(check);

check();
resizeObserver.observe(el);

return () => {
resizeObserver.unobserve(el);
};
}, [target]);

// Handle viewport detection
useEffect(() => {
if (target !== "viewport") return;

const removeListeners = breakpointKeys.map((bpKey) => {
const data = window.matchMedia(`(min-width: ${breakpoints[bpKey]}px)`);

setActiveBreakpoints((v) => ({
...v,
[bpKey]: data.matches,
}));

const listener = (e: MediaQueryListEventMap["change"]): void => {
setActiveBreakpoints((v) => ({
...v,
[bpKey]: e.matches,
}));
};

data.addEventListener("change", listener);

const removeListener = (): void => {
data.removeEventListener("change", listener);
};

return removeListener;
});

return () => {
removeListeners.forEach((removeListener) => {
removeListener();
});
};
}, [target]);

return activeBreakpoints;
};
5 changes: 5 additions & 0 deletions packages/components/responsive/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./components/ResponsiveFragment";

export * from "./hooks/responsive";

export * from "./hooks/responsive-props";
65 changes: 65 additions & 0 deletions packages/components/responsive/src/styles/_mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@use "@react-ck/theme";
@use "@react-ck/text";

$border-radius: theme.get-css-var(card, border-radius);

@mixin shadow-small {
// TODO: theme shadows
box-shadow: 0 1px 3px 0 #00000029;
}

@mixin shadow-big {
// TODO: theme shadows
box-shadow: 0 1px 6px 0 #00000035;
}

@mixin shadow_transition {
transition: box-shadow 0.3s ease;
}

@mixin base {
@include text.text-base;
@include theme.define-css-var(card, border-radius, theme.get-spacing(1));
@include shadow_transition;

display: block; // force display in case of polimorphy
box-sizing: border-box;
border-radius: $border-radius;
background-color: theme.get-color(neutral-light-1);
}

@mixin content {
padding: theme.get-css-var(card, spacing);
}

// Skins

@mixin skin-bordered {
border: solid theme.get-css-var(spacing, border) theme.get-color(neutral-light-2);
}

@mixin skin-shadowed {
@include shadow-small;
}

@mixin skin-ghost {
@include theme.define-css-var(card, border-radius, 0);
}

// Spacing

@mixin spacing-s {
@include theme.define-css-var(card, spacing, theme.get-spacing(1));
}

@mixin spacing-m {
@include theme.define-css-var(card, spacing, theme.get-spacing(2));
}

@mixin spacing-l {
@include theme.define-css-var(card, spacing, theme.get-spacing(3));
}

@mixin spacing-none {
@include theme.define-css-var(card, spacing, 0);
}
84 changes: 84 additions & 0 deletions packages/components/responsive/src/styles/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@use "@react-ck/theme";
@use "@react-ck/text";
@use "shared";

.root {
@include shared.base;
}

// Interaction styles

.hoverable:hover {
&.bordered {
@include shared.shadow-small;
}

&.shadowed {
@include shared.shadow-big;
}
}

.clickable {
cursor: pointer;
}

// Skins

.bordered {
@include shared.skin-bordered;
}

.shadowed {
@include shared.skin-shadowed;
}

.ghost {
@include shared.skin-ghost;
}

// Variations

.full_height {
height: 100%;
}

.horizontal {
display: flex;
}

.vertical {
.image {
width: 100%;
}
}

// Spacing

.spacing_s {
@include shared.spacing-s;
}

.spacing_m {
@include shared.spacing-m;
}

.spacing_l {
@include shared.spacing-l;
}

.spacing_none {
@include shared.spacing-none;
}

// Content

.content {
@include shared.content;
}

.image {
display: block;
border-top-left-radius: theme.get-css-var(card, border-radius);
border-top-right-radius: theme.get-css-var(card, border-radius);
object-fit: cover;
}
1 change: 1 addition & 0 deletions packages/components/responsive/src/styles/shared.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@forward "mixins";
19 changes: 19 additions & 0 deletions packages/components/responsive/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type breakpointKeys } from "./constants";

export type BaseProps = Record<string, unknown>;

export type ResponsiveTarget = "viewport" | React.RefObject<HTMLElement>;

export type Breakpoint = (typeof breakpointKeys)[number];

export interface ResponsiveProps<T extends BaseProps> {
responsive?: {
target?: ResponsiveTarget;
} & {
[key in Breakpoint]: Partial<T>;
};
}

export type EnabledBreakpointsMapping = {
[key in Breakpoint]: boolean;
};
7 changes: 7 additions & 0 deletions packages/components/responsive/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@react-ck/typescript-config/tsconfig.build.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["./*.d.ts", "./src/**/*", "./src/index.ts*"]
}
4 changes: 4 additions & 0 deletions packages/components/responsive/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@react-ck/typescript-config/tsconfig.json",
"include": ["./**/*"]
}
6 changes: 6 additions & 0 deletions packages/components/responsive/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { getWebpackConfig } from "@react-ck/webpack-config";
import packageJson from "./package.json";

export default getWebpackConfig({
cssHashSalt: packageJson.name,
});
3 changes: 2 additions & 1 deletion packages/docs/stories/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@react-ck/tabs": "^1.0.0",
"@react-ck/theme": "^1.0.0",
"@react-ck/progress": "^1.0.0",
"@react-ck/provisional": "^1.0.0"
"@react-ck/provisional": "^1.0.0",
"@react-ck/responsive": "^1.0.0"
}
}
Loading

0 comments on commit f4f8a10

Please sign in to comment.