Skip to content

Commit

Permalink
#24 - feat: add paginator component
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvandescheur committed Jan 26, 2024
1 parent de86333 commit 696f049
Show file tree
Hide file tree
Showing 14 changed files with 478 additions and 31 deletions.
49 changes: 43 additions & 6 deletions src/components/button/button.scss
Original file line number Diff line number Diff line change
@@ -1,34 +1,52 @@
@use '../../settings/style';
@use "../../settings/style";

.mykn-button {
--mykn-button-color-background: var(--theme-color-primary-800);
--mykn-button-color-shadow: var(--theme-shade-1000);
--mykn-button-color-text: var(--theme-shade-0);
--mykn-button-height: auto;
--mykn-button-width: auto;
--mykn-button-offset: 0px;
--mykn-button-padding-v: var(--spacing-v-s);
--mykn-button-padding-h: var(--spacing-h-s);

align-items: center;
appearance: none;
margin: 0;
padding: var(--spacing-v-s) var(--spacing-h-s);
padding: var(--mykn-button-padding-v) var(--mykn-button-padding-h);
background-color: var(--mykn-button-color-background);
border: 1px solid var(--mykn-button-color-border);
border-radius: 4px;
box-shadow: 0 calc(var(--mykn-button-offset) * -1) var(--mykn-button-color-shadow);
box-shadow: 0 calc(var(--mykn-button-offset) * -1)
var(--mykn-button-color-shadow);
box-sizing: border-box;
color: var(--mykn-button-color-text);
cursor: pointer;
display: inline-flex;
flex-wrap: wrap;
gap: 0.5em;
height: var(--mykn-button-height);
font-family: Inter, sans-serif;
font-size: var(--typography-font-size-body-s);
justify-content: center;
line-height: var(--typography-line-height-body-s);
text-align: center;
text-decoration: none;
transition: all var(--animation-duration-fast) var(--animation-timing-function);
transition: all var(--animation-duration-fast)
var(--animation-timing-function);
transform: translateY(var(--mykn-button-offset));
width: var(--mykn-button-width);

&--square {
--mykn-button-height: calc(
var(--typography-line-height-body-s) + 2 * var(--spacing-v-s)
);
--mykn-button-width: calc(
var(--typography-line-height-body-s) + 2 * var(--spacing-v-s)
);
--mykn-button-padding-v: 0;
--mykn-button-padding-h: 0;
}

&--variant-primary {
&:focus,
Expand All @@ -37,13 +55,32 @@
--mykn-button-offset: -2px;
}

&[aria-expanded=true],
&[aria-expanded="true"],
&:active {
--mykn-button-color-background: var(--theme-color-primary-800);
--mykn-button-offset: 0px;
}
}

&--variant-outline {
--mykn-button-color-background: transparent;
--mykn-button-color-border: var(--theme-shade-700);
--mykn-button-color-shadow: currentColor;
--mykn-button-color-text: var(--typography-color-body);

&:focus,
&:hover {
--mykn-button-color-background: var(--theme-color-primary-200);
--mykn-button-offset: -2px;
}

&[aria-expanded="true"],
&:active {
--mykn-button-color-background: var(--typography-color-background);
--mykn-button-offset: 0px;
}
}

&--variant-transparent {
--mykn-button-color-background: transparent;
--mykn-button-color-shadow: currentColor;
Expand All @@ -55,7 +92,7 @@
--mykn-button-offset: -2px;
}

&[aria-expanded=true],
&[aria-expanded="true"],
&:active {
--mykn-button-color-background: var(--typography-color-background);
--mykn-button-offset: 0px;
Expand Down
9 changes: 6 additions & 3 deletions src/components/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import React, { LegacyRef } from "react";
import "./button.scss";

type BaseButtonProps = {
variant?: "primary" | "transparent";
square?: boolean;
variant?: "primary" | "outline" | "transparent";
};

export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
Expand All @@ -20,11 +21,13 @@ export type ButtonLinkProps = React.AnchorHTMLAttributes<HTMLAnchorElement> &
* @constructor
*/
export const Button = React.forwardRef<HTMLAnchorElement, ButtonProps>(
({ variant = "primary", ...props }, ref) => {
({ square = false, variant = "primary", ...props }, ref) => {
return (
<button
ref={ref as LegacyRef<HTMLButtonElement>}
className={clsx("mykn-button", `mykn-button--variant-${variant}`)}
className={clsx("mykn-button", `mykn-button--variant-${variant}`, {
"mykn-button--square": square,
})}
{...props}
>
{props.children}
Expand Down
4 changes: 2 additions & 2 deletions src/components/form/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type InputProps = Omit<
"value"
> & {
/** Gets called when the value is changed */
onChange?: (event: Event) => void;
onChange?: React.ChangeEventHandler<HTMLInputElement>;

/** Input value. */
value?: string | number;
Expand Down Expand Up @@ -54,7 +54,7 @@ export const Input: React.FC<InputProps> = ({
const detail = type === "file" ? input.files : event.target.value;
const changeEvent = eventFactory("change", detail, true, false, false);
input.dispatchEvent(changeEvent);
onChange && onChange(changeEvent);
onChange && onChange(event);
};

return (
Expand Down
4 changes: 4 additions & 0 deletions src/components/form/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
max-width: 100%;
position: relative;

&--size-fit-content {
width: fit-content;
}

.mykn-icon {
transition: transform var(--animation-duration-medium)
var(--animation-timing-function);
Expand Down
38 changes: 23 additions & 15 deletions src/components/form/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
autoUpdate,
flip,
offset,
size,
size as sizeMiddleware,
useClick,
useDismiss,
useFloating,
Expand All @@ -22,12 +22,15 @@ import { eventFactory } from "../eventFactory";
import "./select.scss";

export type SelectProps = React.HTMLAttributes<HTMLDivElement> & {
/** Input name. */
name: string;

/** Can be used to generate `SelectOption` components from an array of objects. */
options: Option[];

/** The clear value (accessible) label. */
labelClear?: string;

/** Input name. */
name?: string;

/**
* Gets called when the selected option is changed
*
Expand All @@ -38,14 +41,14 @@ export type SelectProps = React.HTMLAttributes<HTMLDivElement> & {
*/
onChange?: (event: Event) => void;

/** The clear value (accessible) label. */
labelClear?: string;
/** Placeholder text. */
placeholder?: string;

/** Whether a value is required, a required select can't be cleared. */
required?: boolean;

/** Placeholder text. */
placeholder?: string;
/** Can be set to `fit-content` to apply auto sizing based on content width. */
size?: "fit-content";

value?: Option["value"] | null;
} & SelectRequiredConditional;
Expand All @@ -63,9 +66,12 @@ type SelectRequiredConditional =
/**
* A single (select) option, can be passed to `Select as array.
*/
export type Option = {
label: string;
value?: React.OptionHTMLAttributes<HTMLOptionElement>["value"];
export type Option<
L = number | string,
V = React.OptionHTMLAttributes<HTMLOptionElement>["value"],
> = {
label: L;
value?: V;
selected?: React.OptionHTMLAttributes<HTMLOptionElement>["selected"]; // TODO
};

Expand All @@ -84,6 +90,7 @@ export const Select: React.FC<SelectProps> = ({
labelClear = "Clear value",
placeholder = "",
required = false,
size,
value = null,
...props
}) => {
Expand All @@ -100,7 +107,7 @@ export const Select: React.FC<SelectProps> = ({
middleware: [
offset(6),
flip(),
size({
sizeMiddleware({
padding: 20,
}),
],
Expand Down Expand Up @@ -173,7 +180,8 @@ export const Select: React.FC<SelectProps> = ({
<>
<div
className={clsx("mykn-select", {
"mykn-select__label--selected": selectedIndex,
"mykn-select--selected": selectedIndex,
[`mykn-select--size-${size}`]: size,
})}
tabIndex={0}
ref={refs.setReference}
Expand All @@ -186,7 +194,7 @@ export const Select: React.FC<SelectProps> = ({
<select
ref={fakeInputRef}
name={name}
value={selectedOptionValue}
defaultValue={selectedOptionValue}
hidden={true}
>
{selectedOptionValue && (
Expand Down Expand Up @@ -269,7 +277,7 @@ const BaseSelectDropdown: React.FC<SelectDropdownProps> = ({
setSelectedIndex,
}) => {
const listRef = React.useRef<Array<HTMLElement | null>>([]);
const listContentRef = React.useRef(options.map((o) => o.label));
const listContentRef = React.useRef(options.map((o) => String(o.label)));
const isTypingRef = React.useRef(false);

const click = useClick(context, { event: "mousedown" });
Expand Down
18 changes: 18 additions & 0 deletions src/components/icon/icon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,22 @@
height: 1.2em;
vertical-align: middle;
width: 1.2em;

&--hidden {
visibility: hidden;
}

&--spin {
animation: spin var(--animation-duration-slow)
var(--animation-timing-function) infinite;
}
}

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
33 changes: 28 additions & 5 deletions src/components/icon/icon.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import * as outlineHeroIcons from "@heroicons/react/24/outline";
import * as solidHeroIcons from "@heroicons/react/24/solid";
import clsx from "clsx";
import React from "react";

import "./icon.scss";

export type IconProps = React.SVGProps<SVGElement>;
export type IconProps = React.SVGProps<SVGElement> & {
/** Whether the icon should be hidden (preserving space). */
hidden?: boolean;

/** Whether the icon should spin. */
spin?: boolean;
};

/**
* Takes Object containing (Heroicons) React.FC components. Each component is
Expand All @@ -14,15 +21,31 @@ export type IconProps = React.SVGProps<SVGElement>;
const mapIcons = (heroIcons: { [index: string]: React.FC }) =>
Object.fromEntries(
Object.entries(heroIcons).map(([name, Component]) => {
const Icon = ({ ...props }: IconProps) => (
<Component className="mykn-icon" {...(props as IconProps)} />
const Icon = ({ hidden, spin, ...props }: IconProps) => (
<Component
className={clsx("mykn-icon", {
"mykn-icon--hidden": hidden,
"mykn-icon--spin": spin,
})}
{...(props as IconProps)}
/>
);

return [name, Icon];
}),
);

const Outline = mapIcons(outlineHeroIcons);
const Solid = mapIcons(solidHeroIcons);
type OutlineType = {
[i in keyof typeof outlineHeroIcons]: React.FC<IconProps>;
};
type SolidType = {
[i in keyof typeof solidHeroIcons]: React.FC<IconProps>;
};

// @ts-expect-error - Hacky way of re-exporting types.
const Outline: OutlineType = mapIcons(outlineHeroIcons);

// @ts-expect-error - Hacky way of re-exporting types.
const Solid: SolidType = mapIcons(solidHeroIcons);

export { Outline, Solid };
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export * from "./layout";
export * from "./logo";
export * from "./navbar";
export * from "./page";
export * from "./paginator";
export * from "./toolbar";
export * from "./typography";
1 change: 1 addition & 0 deletions src/components/paginator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./paginator";
7 changes: 7 additions & 0 deletions src/components/paginator/paginator.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@use "../../settings/style";

.mykn-paginator {
align-items: center;
display: flex;
gap: var(--spacing-h-m);
}
Loading

0 comments on commit 696f049

Please sign in to comment.