Skip to content

Commit

Permalink
working on table row + new hero image
Browse files Browse the repository at this point in the history
  • Loading branch information
indaviande committed Dec 11, 2024
1 parent 4b39ca2 commit 349a17a
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 91 deletions.
2 changes: 1 addition & 1 deletion src/components/extenders/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function Modal({ state, title, description, modal, children, clas
<Dialog.Content
style={vars}
className={mergeClass("fixed top-1/2 left-1/2 z-50 -translate-x-1/2 -translate-y-1/2", className)}>
<Box size="xl" className="!p-md*4 lg:!p-lg*4 shadow-md" {...props}>
<Box size="xl" className="p-lg*4 shadow-md" {...props}>
{title && (
<Dialog.Title asChild={!!title}>
{typeof title === "string" ? <Title h={3}>{title}</Title> : title}
Expand Down
27 changes: 25 additions & 2 deletions src/components/primitives/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
/**
* Box Component
*
* A flexible container component that serves as a fundamental building block for layouts.
* It provides various visual styles and sizing options through variants.
*
* @variants
* content:
* - xs to xl: Content size variants
*
* container:
* - true/false: Controls container behavior
*
* @compoundVariants
* The component uses compound variants to determine border radius (rounded corners):
* - When container=true: Uses both size and content values (rounded-${size}+${content})
* - When container=false: Uses only size value (rounded-${size})
*
* @example
* <Box look="soft" size="md" content="sm" container={true}>
* Content goes here
* </Box>
*/

import { tv } from "tailwind-variants";
import useThemedVariables from "../../hooks/theming/useThemedVariables";
import { mergeClass } from "../../utils/css";
Expand Down Expand Up @@ -57,7 +81,6 @@ export const boxStyles = tv(
]),
),
},
{ twMerge: false },
);

export type BoxProps = Component<Styled<typeof boxStyles> & Themable>;
Expand All @@ -78,7 +101,7 @@ export default function Box({
return (
<div
style={Object.assign(style ?? {}, themeVars)}
className={mergeClass(boxStyles({ look, size, content, container: container !== false }), className)}
className={mergeClass(boxStyles({ look, size, content, container: container !== false }),className)}
{...props}
/>
);
Expand Down
37 changes: 28 additions & 9 deletions src/components/primitives/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import * as RemixIcon from "@remixicon/react";
import { type ReactElement, useMemo } from "react";
import { tv } from "tailwind-variants";
import { mergeClass } from "../..";
import type { Component, Styled } from "../..";
import type { Component, Styled, Themable } from "../..";
import useThemableProps from "../../hooks/theming/useThemableProps";
import Image from "./Image";

export const iconStyles = tv({
base: "flex flex-col border-0 overflow-hidden self-center rounded-sm w-[1em] h-[1em]",
base: "flex flex-col border-0 overflow-hidden self-center rounded-sm w-[1em] h-[1em]",
variants: {
size: {
xs: "",
Expand All @@ -26,20 +27,38 @@ export const iconStyles = tv({
});

export type IconProps = Component<
Styled<typeof iconStyles> & {
src?: string;
remix?: keyof typeof RemixIcon;
},
Styled<typeof iconStyles> &
Themable & {
src?: string;
remix?: keyof typeof RemixIcon;
},
HTMLImageElement
>;

export default function Icon({ rounded, remix, size, src, alt, className, ...props }: IconProps) {
export default function Icon({
rounded,
remix,
size,
src,
alt,
className,
...props
}: IconProps) {
const themeVars = useThemableProps(props);
const styles = useMemo(() => iconStyles({ rounded, size }), [rounded, size]);

const Component = useMemo(() => {
if (remix) return RemixIcon[remix] as () => ReactElement;
return (imageProps: Component<unknown>) => <Image alt={alt} src={src} {...imageProps} />;
return (imageProps: Component<unknown>) => (
<Image alt={alt} src={src} {...imageProps} />
);
}, [remix, alt, src]);

return <Component {...props} className={mergeClass(styles, className)} />;
return (
<Component
{...props}
style={themeVars}
className={mergeClass(styles, className)}
/>
);
}
91 changes: 70 additions & 21 deletions src/components/primitives/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { type PropsWithChildren, type ReactNode, useMemo, useState } from "react";
import {
type PropsWithChildren,
type ReactNode,
useMemo,
useState,
} from "react";
import { useMediaQuery } from "react-responsive";
import { tv } from "tailwind-variants";
import { mergeClass } from "../../utils/css";
Expand Down Expand Up @@ -72,7 +77,12 @@ export type RowProps<T extends Columns> = Component<
>
>;

export function Row<T extends Columns>({ columns, exclude, children, ...props }: RowProps<T>) {
export function Row<T extends Columns>({
columns,
exclude,
children,
...props
}: RowProps<T>) {
const isScreenSmall = useMediaQuery({ maxWidth: 640 });
const [ids, grid, compact] = useMemo(() => {
const cols = Object.keys(columns ?? {}) as (keyof T)[];
Expand All @@ -84,7 +94,7 @@ export function Row<T extends Columns>({ columns, exclude, children, ...props }:
display: "grid",
rowGap: "0px",
gridTemplateColumns: cols
.map(id => {
.map((id) => {
if (exclude?.includes(id)) return;
return columns?.[id]?.size ?? "1fr";
})
Expand All @@ -93,8 +103,8 @@ export function Row<T extends Columns>({ columns, exclude, children, ...props }:
const compactStyle: { display: "grid"; gridTemplateColumns: string } = {
display: "grid",
gridTemplateColumns: cols
.filter(id => !columns?.[id]?.main)
.map(id => columns?.[id]?.compactSize ?? "1fr")
.filter((id) => !columns?.[id]?.main)
.map((id) => columns?.[id]?.compactSize ?? "1fr")
.join(" "),
};

Expand All @@ -112,8 +122,10 @@ export function Row<T extends Columns>({ columns, exclude, children, ...props }:

return (
<Box style={isScreenSmall ? compact : grid} {...divProps}>
{ids?.map(id => {
const element = props[`${String(id)}Column` as keyof typeof props] as ReactNode;
{ids?.map((id) => {
const element = props[
`${String(id)}Column` as keyof typeof props
] as ReactNode;
const { className, main } = columns[id];

if (exclude?.includes(id)) return;
Expand All @@ -123,17 +135,22 @@ export function Row<T extends Columns>({ columns, exclude, children, ...props }:
style={
main && isScreenSmall
? {
gridColumn: `span ${ids?.length - 1} / span ${ids?.length - 1}`,
gridColumn: `span ${ids?.length - 1} / span ${
ids?.length - 1
}`,
}
: {}
}
key={String(id)}
className={[className, "inline-flex items-center"].join(" ")}>
className={[className, "inline-flex items-center"].join(" ")}
>
{element}
</div>
);
})}
{children && <EventBlocker style={{ gridColumn: "1 / -1" }}>{children}</EventBlocker>}
{children && (
<EventBlocker style={{ gridColumn: "1 / -1" }}>{children}</EventBlocker>
)}
</Box>
);
}
Expand Down Expand Up @@ -161,7 +178,7 @@ export function useHeaders<T extends Columns>(
onHeaderClick?: (id: keyof T) => void,
sortBy?: keyof T,
order?: Order,
props?: TableHeaders<T>,
props?: TableHeaders<T>
) {
//TODO: assess if props needs to be updated for columns and how to memo all columns
// biome-ignore lint/correctness/useExhaustiveDependencies: props in dependency would render the memo useless
Expand All @@ -172,15 +189,26 @@ export function useHeaders<T extends Columns>(
for (const id of ids) {
const { name: title, className: _className } = columns[id];
const isSortable = sortable?.includes(id);
const handler = title && isSortable ? () => onHeaderClick?.(id) : undefined;
const handler =
title && isSortable ? () => onHeaderClick?.(id) : undefined;

head[`${id}Column` as keyof TableColumns<T>] = (
<Text className="relative font-text" size="md" interactable={isSortable} onKeyDown={handler} onClick={handler}>
<Text
className="relative font-text"
size="md"
interactable={isSortable}
onKeyDown={handler}
onClick={handler}
>
{props?.[`${id}Header` as keyof TableHeaders<T>] ?? title}
<span className="absolute -right-5 top-1/2 -translate-y-1/2">
{sortable &&
id === sortBy &&
(order === "desc" ? <Icon remix={"RiArrowDropDownLine"} /> : <Icon remix={"RiArrowDropUpLine"} />)}
(order === "desc" ? (
<Icon remix={"RiArrowDropDownLine"} />
) : (
<Icon remix={"RiArrowDropUpLine"} />
))}
</span>
</Text>
);
Expand Down Expand Up @@ -209,21 +237,36 @@ export function Table<T extends Columns>({
const [sortBy, setSortBy] = useState<keyof T | undefined>(sortable?.[0]);

function onHeaderClick(id: keyof T) {
const currentOrder = id !== sortBy ? "desc" : _order === "desc" ? "asc" : "desc";
const currentOrder =
id !== sortBy ? "desc" : _order === "desc" ? "asc" : "desc";

setOrder(currentOrder);
setSortBy(id);
onSort?.(id, currentOrder);
}

// biome-ignore lint/suspicious/noExplicitAny: please forgive this any
const headers = useHeaders(columns, sortable, onHeaderClick, sort ?? sortBy, order ?? _order, props as any);
const headers = useHeaders(
columns,
sortable,
onHeaderClick,
sort ?? sortBy,
order ?? _order,
props as any
);

return (
<List indexOffset={header ? 0 : 1} className={mergeClass(className)} look={look} {...props}>
<List
indexOffset={header ? 0 : 1}
className={mergeClass(className)}
look={look}
{...props}
>
{!!header ? <Box className="bg-auto">{header}</Box> : undefined}
{/* biome-ignore lint/suspicious/noExplicitAny: please forgive this one as well */}
{!hideLabels ? <Row {...(headers as any)} columns={columns} /> : undefined}
{!hideLabels ? (
<Row {...(headers as any)} columns={columns} />
) : undefined}
{children}
{!!footer ? <Box className="bg-auto">{footer}</Box> : undefined}
</List>
Expand All @@ -233,11 +276,17 @@ export function Table<T extends Columns>({
export function createTable<T extends Columns>(columns: T) {
const TemplateTable = (props: Omit<TableProps<T>, "columns"> & ListProps) => (
// biome-ignore lint/suspicious/noExplicitAny: no reasons for it to have type errors
<Table {...(props as any)} columns={columns} />
<Table size="lg" {...(props as any)} columns={columns} />
);

// biome-ignore lint/suspicious/noExplicitAny: no reasons for it to have type errors
const TemplateRow = (props: Omit<RowProps<T>, "columns">) => <Row {...(props as any)} columns={columns} />;
const TemplateRow = (props: Omit<RowProps<T>, "columns">) => (
<Row {...(props as any)} columns={columns} />
);

return [TemplateTable, TemplateRow, Object.keys(columns)] as [typeof TemplateTable, typeof TemplateRow, (keyof T)[]];
return [TemplateTable, TemplateRow, Object.keys(columns)] as [
typeof TemplateTable,
typeof TemplateRow,
(keyof T)[]
];
}
33 changes: 33 additions & 0 deletions src/components/primitives/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
/**
* @component Tabs
* @description A flexible and customizable tabs component that supports different visual styles and navigation.
*
* @features
* - Multiple visual styles (soft, base, bold, tint, hype)
* - Responsive design with Tailwind CSS
* - Built-in navigation support using Remix's Link component
* - Customizable themes
* - Accessibility features including keyboard navigation
*
* @usage
* ```tsx
* <Tabs
* tabs={[
* { label: "Tab 1", link: "/tab1", key: "1" },
* { label: "Tab 2", link: "/tab2", key: "2" }
* ]}
* look="soft"
* theme="custom"
* />
* ```
*
* @props
* - tabs: Array of tab items with label, link, and key
* - look: Visual style variant (soft, base, bold, tint, hype)
* - size: Size variant
* - theme: Custom theme override
* - className: Additional CSS classes
* - disabled: Whether the tabs are disabled
* - to: Default navigation path
*/

import { Link, useLocation } from "@remix-run/react";

import type { ReactNode } from "react";
Expand Down
Loading

0 comments on commit 349a17a

Please sign in to comment.