Skip to content

Commit

Permalink
write docs for hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
PixeledCode committed Mar 22, 2024
1 parent 2ae16ff commit 5161db2
Show file tree
Hide file tree
Showing 15 changed files with 166 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import React from 'react';
import Link from 'next/link';
import { useMetaKeyPress } from '@/hooks/use-meta-key-press';
import { Session } from 'next-auth';
import { signIn, signOut, useSession } from 'next-auth/react';
import {
Expand All @@ -22,6 +21,7 @@ import {
Spinner,
Text,
} from 'opub-ui';
import { useMetaKeyPress } from 'opub-ui/utils';

import { Icons } from '@/components/icons';

Expand Down
2 changes: 1 addition & 1 deletion packages/opub-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "opub-ui",
"description": "OPub UI Library",
"version": "0.3.16",
"version": "0.3.17",
"private": false,
"license": "MIT",
"author": "CivicDataLab <tech@civicdatalab.in>",
Expand Down
23 changes: 21 additions & 2 deletions packages/opub-ui/src/utils/css.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { ClassNameValue, twMerge } from 'tailwind-merge';

export function variationName(name: string, value: string) {
type VariationName = (name: string, value: string) => string;

/**
* Constructs a variation name by capitalizing the first letter of the value and appending it to the name.
*
* @example
* // returns "colorRed"
* variationName("color", "red");
*/
export const variationName: VariationName = (name: string, value: string) => {
return `${name}${value.charAt(0).toUpperCase()}${value.slice(1)}`;
}
};

/**
* A utility function that merges multiple class names into a single string.
*
* This function uses the `twMerge` function from the `twin.macro` library to merge the class names.
* It accepts an array of class names, which can be strings, arrays of strings, or objects with class names as keys and boolean values.
*
* @example
* // returns "text-red-500 bg-blue-500"
* cn('text-red-500', 'bg-blue-500');
*/
export function cn(...inputs: ClassNameValue[]) {
return twMerge(inputs);
}
8 changes: 4 additions & 4 deletions packages/opub-ui/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function mergeRefs<T = any>(
* @param {number} max - The upper boundary.
* @return {number} The clamped number.
*/
export function clamp(number: number, min: number, max: number) {
export function clamp(number: number, min: number, max: number): number {
if (number < min) return min;
if (number > max) return max;
return number;
Expand All @@ -50,7 +50,7 @@ export function clamp(number: number, min: number, max: number) {
* // returns "World"
* capitalize("WORLD");
*/
export function capitalize(word = '') {
export function capitalize(word: string = ''): string {
const wordLower = word.toLowerCase();
return wordLower.charAt(0).toUpperCase() + wordLower.slice(1);
}
Expand All @@ -65,7 +65,7 @@ export function capitalize(word = '') {
* // returns [0, 1, 2, 3, 4]
* range(5);
*/
export const range = (len: number) => {
export const range = (len: number): number[] => {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(i);
Expand All @@ -84,7 +84,7 @@ export const range = (len: number) => {
* groupBy([{ 'group': 'group1', 'value': 1 }, { 'group': 'group2', 'value': 2 }], 'group');
* // returns { 'group1': [{ 'group': 'group1', 'value': 1 }], 'group2': [{ 'group': 'group2', 'value': 2 }] }
*/
export const groupBy = function (arr: any[], criteria: string) {
export const groupBy = function (arr: any[], criteria: string): object {
return arr.reduce(function (obj, item) {
var key = item[criteria];
// If the key doesn't exist yet, create it
Expand Down
2 changes: 1 addition & 1 deletion packages/opub-ui/src/utils/hooks/use-forward-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import React from 'react';
export const useForwardRef = <T>(
ref: React.ForwardedRef<T>,
initialValue: any = null
) => {
): React.MutableRefObject<T> => {
const targetRef = React.useRef<T>(initialValue);

React.useEffect(() => {
Expand Down
14 changes: 13 additions & 1 deletion packages/opub-ui/src/utils/hooks/use-is-mac.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import React from 'react';

export const useIsMac = () => {
/**
* A React Hook that determines if the current user's operating system is macOS.
*
* This is determined by checking the user agent string for the presence of 'Mac'.
*
* @return {boolean} A state value indicating whether the current user's operating system is macOS.
*
* @example
* // returns true if the user's operating system is macOS
* // returns false otherwise
* const isMacOS = useIsMac();
*/
export const useIsMac = (): boolean => {
const [isMac, setIsMac] = React.useState(false);
React.useEffect(() => {
setIsMac(window.navigator.userAgent.includes('Mac'));
Expand Down
15 changes: 14 additions & 1 deletion packages/opub-ui/src/utils/hooks/use-isomorphic-layout-effect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
'use client';

import { isServer } from '../target';
import { useEffect, useLayoutEffect } from 'react';

import { isServer } from '../target';

/**
* A custom hook that uses `useLayoutEffect` on the client and `useEffect` on the server.
*
* This is useful for avoiding warnings about `useLayoutEffect` not working on the server,
* while still getting the benefits of `useLayoutEffect` when running on the client.
*
* @example
* // In a component...
* useIsomorphicLayoutEffect(() => {
* // Your effect here...
* });
*/
export const useIsomorphicLayoutEffect = isServer ? useEffect : useLayoutEffect;
31 changes: 18 additions & 13 deletions packages/opub-ui/src/utils/hooks/use-key-detect.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { useEffect, useState } from 'react';

export function useKeyDetect(): { key: string; metaKey?: boolean } {
/**
* A custom React Hook that listens for a key press event and returns the key pressed.
*
* The hook listens for the 'keydown' event on the window object. When a key is pressed, it updates the state with the key pressed.
* The state is cleared after 1 millisecond.
*
* @return {object} An object with one property: `key` (the key that was pressed).
*
* @example
* // In a component...
* const { key } = useKeyDetect();
* // `key` is the key that was pressed
*/
export function useKeyDetect(): { key: string } {
const [keyPressed, setKeyPressed] = useState<{
key: string;
metaKey?: boolean;
}>({
key: '',
metaKey: false,
});
function downHandler({
key,
metaKey,
}: {
key: string;
metaKey?: boolean;
}): void {
setKeyPressed({ key, metaKey });
function downHandler({ key }: { key: string; metaKey?: boolean }): void {
setKeyPressed({ key });

// clear key after 1 milisecond
setTimeout(() => {
setKeyPressed({ key: '', metaKey: false });
setKeyPressed({ key: '' });
}, 1);
}

Expand All @@ -32,5 +37,5 @@ export function useKeyDetect(): { key: string; metaKey?: boolean } {
window.removeEventListener('keydown', downHandler);
};
}, []); // Empty array ensures that effect is only run on mount and unmount
return { key: keyPressed.key, metaKey: keyPressed.metaKey };
return { key: keyPressed.key };
}
13 changes: 11 additions & 2 deletions packages/opub-ui/src/utils/hooks/use-lock-body.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import * as React from 'react';

// @see https://usehooks.com/useLockBodyScroll.
export function useLockBody() {
/**
* A React Hook that locks the body scroll by setting the overflow style to 'hidden'.
*
* The hook uses `useLayoutEffect` to change the body's overflow style to 'hidden' when the component mounts, effectively locking the body scroll.
* When the component unmounts, the hook restores the original overflow style, unlocking the body scroll.
*
* @example
* // In a component...
* useLockBody();
* // The body scroll is now locked. It will be unlocked when the component unmounts.
*/ export function useLockBody() {
React.useLayoutEffect((): (() => void) => {
const originalStyle: string = window.getComputedStyle(
document.body
Expand Down
12 changes: 12 additions & 0 deletions packages/opub-ui/src/utils/hooks/use-meta-key-press.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import React, { useState } from 'react';

/**
* A custom React Hook that listens for a specific key press and triggers a function when the key is pressed.
* On Mac, it listens for the Meta key (Command ⌘) and on other platforms, it listens for the Control key.
*
* @param {string} targetKey - The specific key to listen for.
* @param {() => void} [fn] - The function to execute when the target key is pressed. Optional.
* @return {boolean} A state value indicating whether the target key is currently pressed.
*
* @example
* // Listen for 's' key press and log a message when it's pressed with the Meta/Control key
* useMetaKeyPress('s', () => console.log('Save command triggered'));
*/
export function useMetaKeyPress(targetKey: string, fn?: () => void): boolean {
const [keyPressed, setKeyPressed] = useState(false);

Expand Down
18 changes: 16 additions & 2 deletions packages/opub-ui/src/utils/hooks/use-mounted.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import * as React from 'react';
import React from 'react';

export function useMounted() {
/**
* A React Hook that tracks if the component is currently mounted.
*
* The hook uses `useState` to create a `mounted` state variable, initially set to `false`.
* It then uses `useEffect` to set `mounted` to `true` when the component mounts.
*
* @return {boolean} A state value indicating whether the component is currently mounted.
*
* @example
* // In a component...
* const isMounted = useMounted();
* // `isMounted` is true if the component is currently mounted, false otherwise.
*/

export function useMounted(): boolean {
const [mounted, setMounted] = React.useState(false);

React.useEffect(() => {
Expand Down
15 changes: 15 additions & 0 deletions packages/opub-ui/src/utils/hooks/use-on-click-outside.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import React from 'react';

/**
* A React Hook that triggers a handler function when a click event occurs outside of the specified element.
*
* The hook uses `useEffect` to add a click event listener to the document when the component mounts.
* The event listener checks if the click event occurred inside the specified element. If it did not, it calls the handler function.
* When the component unmounts, the hook removes the event listener.
*
* @param {React.RefObject<HTMLElement>} ref - A ref object representing the element to detect clicks outside of.
* @param {(event: MouseEvent | TouchEvent) => void} handler - The function to execute when a click event occurs outside the specified element.
*
* @example
* // In a component...
* const ref = useRef(null);
* useOnClickOutside(ref, () => console.log('Clicked outside'));
*/
export function useOnClickOutside(
ref: React.RefObject<HTMLElement>,
handler: (event: MouseEvent | TouchEvent) => void
Expand Down
27 changes: 23 additions & 4 deletions packages/opub-ui/src/utils/hooks/use-toggle.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
'use client';

import { useState, useCallback } from 'react';
import { useCallback, useState } from 'react';

/**
* Returns a stateful value, and a set of memoized functions to toggle it,
* set it to true and set it to false
* A React Hook that provides a boolean state value and functions to toggle it, set it to true, and set it to false.
*
* The hook uses `useState` to create a `value` state variable, initially set to the provided `initialState`.
* It also provides three functions: `toggle` (which toggles the `value`), `setTrue` (which sets `value` to true), and `setFalse` (which sets `value` to false).
* These functions are wrapped in `useCallback` to prevent unnecessary re-renders.
*
* @param {boolean} initialState - The initial state value.
* @return {object} An object containing the `value` state value and the `toggle`, `setTrue`, and `setFalse` functions.
*
* @example
* // In a component...
* const { value, toggle, setTrue, setFalse } = useToggle(false);
* // `value` is the current state value
* // `toggle` is a function that toggles the state value
* // `setTrue` is a function that sets the state value to true
* // `setFalse` is a function that sets the state value to false
*/
export function useToggle(initialState: boolean) {
export function useToggle(initialState: boolean): {
value: boolean;
toggle: () => void;
setTrue: () => void;
setFalse: () => void;
} {
const [value, setState] = useState(initialState);

return {
Expand Down
17 changes: 15 additions & 2 deletions packages/opub-ui/src/utils/hooks/use-window-size.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// https://usehooks.com/useWindowSize/
import { useEffect, useState } from 'react';

// Define general type for useWindowSize hook, which includes width and height
Expand All @@ -7,7 +6,21 @@ export interface Size {
height: number | undefined;
}

// Hook
/**
* A custom React Hook that provides the current window size.
*
* The hook uses `useState` to create a `windowSize` state variable, initially set to an object with `width` and `height` both undefined.
* It then uses `useEffect` to add a 'resize' event listener to the window when the component mounts. This event listener updates `windowSize` with the current window size whenever the window is resized.
* The initial window size is set by calling the event handler immediately after adding the event listener.
* The event listener is removed when the component unmounts.
*
* @return {Size} An object containing the current window width and height.
*
* @example
* // In a component...
* const { width, height } = useWindowSize();
* // `width` and `height` are the current window width and height, respectively.
*/
export function useWindowSize(): Size {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
Expand Down
2 changes: 1 addition & 1 deletion packages/opub-ui/src/utils/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
* // returns false in a browser environment
* const serverCheck = isServer;
*/
export const isServer =
export const isServer: boolean =
typeof window === 'undefined' || typeof document === 'undefined';

0 comments on commit 5161db2

Please sign in to comment.