Compare commits

..

10 Commits

123 changed files with 4028 additions and 1314 deletions

5
.gitignore vendored
View File

@@ -7,10 +7,7 @@ dist
*.local *.local
.tanstack .tanstack
*.tgz *.tgz
test/coverage coverage
# Editor directories and files
.vscode
# Sonarqube # Sonarqube
sonarBuild.sh sonarBuild.sh

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"sonarlint.connectedMode.project": {
"connectionId": "mattrixwvSonarqube",
"projectKey": "MattrixwvReactComponents"
}
}

View File

@@ -1 +1,5 @@
# Mattrixwv React Components
[![Quality Gate Status](https://sonarqube.mattrixwv.com/api/project_badges/measure?project=MattrixwvReactComponents&metric=alert_status&token=sqb_e8b5362c32319b0612536e683cdbe2a77ec5bb32)](https://sonarqube.mattrixwv.com/dashboard?id=MattrixwvReactComponents)
Under Construction Under Construction

View File

@@ -1,10 +1,9 @@
import type { CheckboxProps } from "$/types/InputTypes"; import type { CheckboxProps } from "$/types/InputTypes";
import { Checkbox } from "@headlessui/react"; import { Checkbox, Field, Label } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
export default function MattrixwvCheckbox({ export default function MattrixwvCheckbox({
id = crypto.randomUUID().replaceAll("-", ""),
className, className,
labelClassName, labelClassName,
name, name,
@@ -19,65 +18,68 @@ export default function MattrixwvCheckbox({
children children
}: Readonly<CheckboxProps>){ }: Readonly<CheckboxProps>){
return ( return (
<Checkbox <Field
id={id} className="flex flex-row items-center justify-center gap-x-2"
className={clsx(
"group",
"flex flex-row items-center justify-start gap-x-2",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
{
"cursor-pointer": !disabled,
"cursor-not-allowed": disabled
}
)}
name={name}
checked={checked}
defaultChecked={defaultChecked}
onChange={onChange}
value={value}
disabled={disabled}
aria-labelledby={`${id}Label`}
> >
{/* Checkbox */} <Checkbox
<div data-testid="mattrixwv-checkbox"
className={clsx( className={clsx(
className, "group",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
{ {
"border rounded": showBox "cursor-pointer": !disabled,
}, "cursor-not-allowed": disabled
{
"": size === "none",
"size-3": size === "xs",
"size-4": size === "sm",
"size-5": size === "md",
"size-6": size === "lg",
"size-7": size === "xl"
} }
)} )}
name={name}
checked={checked}
defaultChecked={defaultChecked}
onChange={onChange}
value={value}
disabled={disabled}
> >
<svg {/* Checkbox */}
viewBox="0 0 14 14" <div
fill="none" data-testid="mattrixwv-checkbox-graphic"
aria-hidden="true" className={clsx(
className,
{
"border rounded": showBox
},
{
"": size === "none",
"size-3": size === "xs",
"size-4": size === "sm",
"size-5": size === "md",
"size-6": size === "lg",
"size-7": size === "xl"
}
)}
> >
<path <svg
d="M3 8L6 11L11 3.5" viewBox="0 0 14 14"
strokeWidth={strokeWidth} fill="none"
strokeLinecap="round" aria-hidden="true"
strokeLinejoin="round" >
/> <path
</svg> d="M3 8L6 11L11 3.5"
</div> strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</Checkbox>
{/* Label */} {/* Label */}
{ {
children && children &&
<span <Label
id={`${id}Label`} data-testid="mattrixwv-checkbox-label"
className={labelClassName} className={labelClassName}
> >
{children} {children}
</span> </Label>
} }
</Checkbox> </Field>
); );
} }

View File

@@ -1,41 +1,50 @@
import { DangerButton } from "$/component/button";
import type { FileInputProps } from "$/types/InputTypes"; import type { FileInputProps } from "$/types/InputTypes";
import { humanReadableBytes } from "$/util/FileUtil"; import { humanReadableBytes } from "$/util/FileUtil";
import clsx from "clsx"; import clsx from "clsx";
import { useEffect, useRef, useState } from "react"; import { useRef, useState } from "react";
import { MdClose } from "react-icons/md";
export default function DragAndDropFileInput({ export default function DragAndDropFileInput({
id, id,
className, className,
name, name,
ariaLabel,
minSize, minSize,
maxSize, maxSize,
showFileName, showFileName = true,
showSize, showSize = true,
onChange, onChange,
disabled, disabled,
children children
}: FileInputProps){ }: Readonly<FileInputProps>){
const [ file, setFile ] = useState<File>(); const [ file, setFile ] = useState<File>();
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
onChange?.(file);
}, [ file, onChange ]);
return ( return (
<label <label
className={clsx( className={clsx(
"flex flex-col items-center justify-center border-2 rounded-lg w-full h-full cursor-pointer", "flex flex-col items-center justify-center border-2 rounded-lg cursor-pointer",
"data-drag:border-primary data-drag:text-primary",
className className
)} )}
onDragOver={(e) => e.preventDefault()} onDragOver={(e) => { e.preventDefault(); e.currentTarget.dataset.drag = "true"; }}
onDragLeave={(e) => { e.preventDefault(); delete e.currentTarget.dataset.drag; }}
onDrop={(e) => { onDrop={(e) => {
e.preventDefault(); e.preventDefault();
setFile(e.dataTransfer.files[0]); delete e.currentTarget.dataset.drag;
const currentFile = e.dataTransfer.files[0];
setFile(currentFile);
if ((minSize && currentFile.size < minSize) || (maxSize && currentFile.size > maxSize)) return;
onChange?.(currentFile);
if(inputRef.current){ inputRef.current.files = e.dataTransfer.files; } if(inputRef.current){ inputRef.current.files = e.dataTransfer.files; }
}} }}
aria-label={ariaLabel}
> >
<input <input
ref={inputRef} ref={inputRef}
@@ -43,24 +52,40 @@ export default function DragAndDropFileInput({
id={id} id={id}
className="sr-only" className="sr-only"
name={name} name={name}
onChange={(e) => setFile(e.target.files?.[0])} onChange={(e) => {
const currentFile = e.target.files?.[0];
setFile(currentFile);
if ((minSize && currentFile && currentFile.size < minSize) || (maxSize && currentFile && currentFile.size > maxSize)) return;
onChange?.(currentFile);
}}
disabled={disabled} disabled={disabled}
/> />
<div <div
className="flex flex-col items-center justify-between w-full h-full px-2" className="flex flex-col items-center justify-between px-2"
> >
{children} <div className="flex flex-row items-center justify-center">
{children}
</div>
<div <div
className="flex flex-row items-center justify-between gap-x-8 w-full" className="flex flex-row items-center justify-between gap-x-2 w-full"
> >
{ {
showFileName && showFileName &&
<div <div className="flex flex-row items-center justify-center gap-x-2">
className="text-center"
>
{file?.name} {file?.name}
</div> </div>
} }
{
file &&
<DangerButton
className="mr-4"
shape="square"
variant="icon"
onClick={(e) => { e.preventDefault(); setFile(undefined); onChange?.(undefined); }}
>
<MdClose size={22} className="fill-danger"/>
</DangerButton>
}
{ {
showSize && showSize &&
<div <div

View File

@@ -1,22 +1,25 @@
import { SecondaryButton } from "$/component/button"; import { DangerButton, SecondaryButton } from "$/component/button";
import type { FileInputProps } from "$/types/InputTypes"; import type { FileInputProps } from "$/types/InputTypes";
import { humanReadableBytes } from "$/util/FileUtil"; import { humanReadableBytes } from "$/util/FileUtil";
import clsx from "clsx"; import clsx from "clsx";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { FaRegFolderOpen } from "react-icons/fa6";
import { MdClose } from "react-icons/md";
export default function FileInput({ export default function FileInput({
id, id,
className, className,
name, name,
ariaLabel,
minSize, minSize,
maxSize, maxSize,
showFileName, showFileName = true,
showSize, showSize = true,
onChange, onChange,
disabled, disabled,
children children
}: FileInputProps){ }: Readonly<FileInputProps>){
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [ file, setFile ] = useState<File>(); const [ file, setFile ] = useState<File>();
@@ -24,7 +27,7 @@ export default function FileInput({
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-row items-center justify-between w-full border-2 rounded-lg", "flex flex-row items-center justify-between border-2 rounded-lg",
className className
)} )}
> >
@@ -34,7 +37,13 @@ export default function FileInput({
type="file" type="file"
className="sr-only" className="sr-only"
name={name} name={name}
onChange={(e) => { setFile(e.target.files?.[0]); onChange?.(e.target.files?.[0]); }} aria-label={ariaLabel}
onChange={(e) => {
const currentFile = e.target.files?.[0];
setFile(currentFile);
if ((minSize && currentFile && currentFile.size < minSize) || (maxSize && currentFile && currentFile.size > maxSize)) return;
onChange?.(currentFile);
}}
disabled={disabled} disabled={disabled}
/> />
<div <div
@@ -48,6 +57,16 @@ export default function FileInput({
showFileName && showFileName &&
<div>{file?.name}</div> <div>{file?.name}</div>
} }
{
file &&
<DangerButton
shape="square"
variant="icon"
onClick={(e) => { e.preventDefault(); setFile(undefined); onChange?.(undefined); }}
>
<MdClose size={22} className="fill-danger"/>
</DangerButton>
}
{ {
!children && !showFileName && !children && !showFileName &&
<>&nbsp;</> <>&nbsp;</>
@@ -56,12 +75,13 @@ export default function FileInput({
showSize && showSize &&
<div <div
className={clsx( className={clsx(
"ml-4",
{ {
"text-red-600": minSize && file?.size && file?.size < minSize, "text-danger": minSize && file?.size && file?.size < minSize,
"text-red-600 ": maxSize && file?.size && file?.size > maxSize, "text-danger ": maxSize && file?.size && file?.size > maxSize,
"text-green-600": minSize && !maxSize && file?.size && file?.size > minSize, "text-success": minSize && !maxSize && file?.size && file?.size > minSize,
"text-green-600 ": !minSize && maxSize && file?.size && file?.size < maxSize, "text-success ": !minSize && maxSize && file?.size && file?.size < maxSize,
" text-green-600": minSize && maxSize && file?.size && file?.size > minSize && file?.size < maxSize " text-success": minSize && maxSize && file?.size && file?.size > minSize && file?.size < maxSize
} }
)} )}
> >
@@ -75,10 +95,13 @@ export default function FileInput({
<SecondaryButton <SecondaryButton
className="text-nowrap rounded-r-lg" className="text-nowrap rounded-r-lg"
rounding="none" rounding="none"
shape="square"
size="lg"
onClick={() => { inputRef.current?.click(); }} onClick={() => { inputRef.current?.click(); }}
disabled={disabled} disabled={disabled}
aria-label="Select File"
> >
Click Me <FaRegFolderOpen />
</SecondaryButton> </SecondaryButton>
</div> </div>
</div> </div>

View File

@@ -10,38 +10,42 @@ export default function NumberInput({
name, name,
min, min,
max, max,
defaultValue, step,
prefix,
suffix,
value, value,
onChange, onChange,
disabled, disabled,
children children
}: NumberInputProps){ }: Readonly<NumberInputProps>){
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-row items-center justify-center rounded-lg border-2 w-full", "flex flex-row items-center justify-center rounded-lg border-2",
className className
)} )}
> >
<div <div className="relative flex flex-row items-center justify-center px-2 py-1 w-full">
className="relative flex flex-row items-center justify-center px-2 py-1 w-full" <div className="flex flex-row items-center justify-start">
> { prefix && <span>{prefix}</span> }
<input <input
type="number" type="number"
id={id} id={id}
className={clsx( className={clsx(
"peer bg-transparent outline-none placeholder-transparent w-full", "peer bg-transparent outline-none placeholder-transparent w-full",
"[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none", "[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
inputClassName inputClassName
)} )}
name={name} name={name}
min={min} min={min}
max={max} max={max}
defaultValue={defaultValue} step={step}
value={value} value={value}
onChange={(e) => onChange?.(e.target.valueAsNumber)} onChange={(e) => onChange(e.target.valueAsNumber || 0)}
disabled={disabled} disabled={disabled}
/> />
{ suffix && <span>{suffix}</span> }
</div>
<label <label
className={clsx( className={clsx(
"absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default", "absolute ml-2 -top-3 left-0 text-sm rounded-md px-1 select-none cursor-default",

View File

@@ -9,28 +9,31 @@ export default function NumberSlider({
min, min,
max, max,
step, step,
defaultValue,
value, value,
onChange, onChange,
disabled disabled,
}: NumberSliderProps){ ariaLabel
}: Readonly<NumberSliderProps>){
return ( return (
<input <input
type="range" type="range"
id={id} id={id}
className={clsx( className={clsx(
"w-full appearance-none [-moz-range-thumb:background:#04AA6D]", "appearance-none [-moz-range-thumb:background:#04AA6D]",
"h-6 bg-blue-300 accent-blue-600", "h-5 px-0.5 rounded-full bg-primary",
className className
)} )}
name={name} name={name}
min={min} min={min}
max={max} max={max}
step={step} step={step}
defaultValue={defaultValue}
value={value} value={value}
onChange={(e) => onChange?.(e.target.valueAsNumber)} onChange={(e) => onChange(e.target.valueAsNumber || 0)}
disabled={disabled} disabled={disabled}
aria-label={ariaLabel}
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
/> />
); );
} }

View File

@@ -3,15 +3,13 @@ import { ListboxOption } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
export default function OptionInput(props: OptionInputProps){ export default function OptionInput({
const { id,
id, className,
className, value,
value, disabled,
children children
} = props; }: Readonly<OptionInputProps>){
return ( return (
<ListboxOption <ListboxOption
id={id} id={id}
@@ -20,6 +18,7 @@ export default function OptionInput(props: OptionInputProps){
className className
)} )}
value={value} value={value}
disabled={disabled}
> >
{children} {children}
</ListboxOption> </ListboxOption>

View File

@@ -4,28 +4,28 @@ import clsx from "clsx";
import { BsChevronDown } from "react-icons/bs"; import { BsChevronDown } from "react-icons/bs";
export default function SelectInput(props: SelectInputProps){ export default function SelectInput({
const { placeholder,
label, value,
value, onChange,
onChange, disabled,
children children
} = props; }: Readonly<SelectInputProps>){
return ( return (
<Listbox <Listbox
value={value} value={value}
onChange={onChange} onChange={onChange}
disabled={disabled}
> >
<ListboxButton <ListboxButton
className={clsx( className={clsx(
"group relative flex flex-row items-center justify-between w-full", "group relative flex flex-row items-center justify-between w-full",
"border-2 px-2 py-1 rounded-lg" "border-2 px-2 py-1 rounded-lg",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
//"not-data-open:rounded-lg data-open:rounded-t-lg" //"not-data-open:rounded-lg data-open:rounded-t-lg"
)} )}
> >
<span>{label}</span> <span>{placeholder}</span>
<span className="block group-data-open:rotate-180 transition-transform duration-250"><BsChevronDown size={22}/></span> <span className="block group-data-open:rotate-180 transition-transform duration-250"><BsChevronDown size={22}/></span>
</ListboxButton> </ListboxButton>
<ListboxOptions <ListboxOptions

View File

@@ -1,23 +1,27 @@
import type { TextAreaProps } from "$/types/InputTypes"; import type { TextAreaProps } from "$/types/InputTypes";
import clsx from "clsx"; import clsx from "clsx";
import { useId } from "react";
export default function TextArea({ export default function TextArea({
id = crypto.randomUUID().replaceAll("-", ""), id,
className, className,
inputClassName, inputClassName,
labelClassName, labelClassName,
name, name,
maxLength, maxLength,
rows, rows = 3,
cols, cols,
spellCheck, spellCheck,
placeholder, placeholder,
defaultValue,
value, value,
onChange, onChange,
disabled disabled
}: TextAreaProps){ }: Readonly<TextAreaProps>){
const componentId = useId();
const activeId = id ?? componentId;
return ( return (
<div <div
className={clsx( className={clsx(
@@ -29,7 +33,7 @@ export default function TextArea({
className="relative flex flex-row items-center justify-center px-2 py-1 w-full" className="relative flex flex-row items-center justify-center px-2 py-1 w-full"
> >
<textarea <textarea
id={id} id={activeId}
className={clsx( className={clsx(
"peer bg-transparent outline-none placeholder-transparent w-full", "peer bg-transparent outline-none placeholder-transparent w-full",
inputClassName inputClassName
@@ -39,7 +43,6 @@ export default function TextArea({
maxLength={maxLength} maxLength={maxLength}
rows={rows} rows={rows}
cols={cols} cols={cols}
defaultValue={defaultValue}
value={value} value={value}
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}
@@ -54,7 +57,7 @@ export default function TextArea({
labelClassName labelClassName
)} )}
style={{ transitionProperty: "top, left, font-size, line-height", transitionTimingFunction: "cubic-bezier(0.4 0, 0.2, 1)", transitionDuration: "250ms" }} style={{ transitionProperty: "top, left, font-size, line-height", transitionTimingFunction: "cubic-bezier(0.4 0, 0.2, 1)", transitionDuration: "250ms" }}
htmlFor={id} htmlFor={activeId}
> >
{placeholder} {placeholder}
</label> </label>

View File

@@ -1,9 +1,10 @@
import type { TextInputProps } from "$/types/InputTypes"; import type { TextInputProps } from "$/types/InputTypes";
import clsx from "clsx"; import clsx from "clsx";
import { useId } from "react";
export default function TextInput({ export default function TextInput({
id = crypto.randomUUID().replaceAll("-", ""), id,
className, className,
inputClassName, inputClassName,
labelClassName, labelClassName,
@@ -11,11 +12,14 @@ export default function TextInput({
maxLength, maxLength,
spellCheck, spellCheck,
placeholder, placeholder,
defaultValue,
value, value,
onChange, onChange,
disabled disabled
}: TextInputProps){ }: Readonly<TextInputProps>){
const componentId = useId();
const activeId = id ?? componentId;
return ( return (
<div <div
className={clsx( className={clsx(
@@ -28,7 +32,7 @@ export default function TextInput({
> >
<input <input
type="text" type="text"
id={id} id={activeId}
className={clsx( className={clsx(
"peer bg-transparent outline-none placeholder-transparent w-full", "peer bg-transparent outline-none placeholder-transparent w-full",
inputClassName inputClassName
@@ -36,7 +40,6 @@ export default function TextInput({
name={name} name={name}
placeholder={placeholder} placeholder={placeholder}
maxLength={maxLength} maxLength={maxLength}
defaultValue={defaultValue}
value={value} value={value}
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}
@@ -51,7 +54,7 @@ export default function TextInput({
labelClassName labelClassName
)} )}
style={{ transitionProperty: "top, left, font-size, line-height", transitionTimingFunction: "cubic-bezier(0.4 0, 0.2, 1)", transitionDuration: "250ms" }} style={{ transitionProperty: "top, left, font-size, line-height", transitionTimingFunction: "cubic-bezier(0.4 0, 0.2, 1)", transitionDuration: "250ms" }}
htmlFor={id} htmlFor={activeId}
> >
{placeholder} {placeholder}
</label> </label>

View File

@@ -1,22 +1,32 @@
import type { LoadingBarsProps } from "$/types/LoadingTypes"; import type { LoadingBarsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function CenterGrowingBars({ export default function CenterGrowingBars({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingBarsProps){ ariaLabel = "Loading"
}: Readonly<LoadingBarsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-scale-middle.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-scale-middle.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -26,22 +36,22 @@ export default function CenterGrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle3_${id}.begin+${dur * 2 / 3}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle3_${id}.begin+${dur * 2 / 3}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
@@ -52,22 +62,22 @@ export default function CenterGrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration / 3}s`} begin={`rectangle3_${id}.begin+${dur / 3}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration / 3}s`} begin={`rectangle3_${id}.begin+${dur / 3}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
@@ -78,23 +88,23 @@ export default function CenterGrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle3_${id}`} id={`rectangle3_${id}`}
begin={`0;rectangle5_${id}.end-${animationDuration / 6}s`} begin={`0;rectangle5_${id}.end-${dur / 6}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
<animate <animate
begin={`0;rectangle5_${id}.end-${animationDuration / 6}s`} begin={`0;rectangle5_${id}.end-${dur / 6}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
@@ -105,22 +115,22 @@ export default function CenterGrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration / 3}s`} begin={`rectangle3_${id}.begin+${dur / 3}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration / 3}s`} begin={`rectangle3_${id}.begin+${dur / 3}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
@@ -131,23 +141,23 @@ export default function CenterGrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle5_${id}`} id={`rectangle5_${id}`}
begin={`rectangle3_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle3_${id}.begin+${dur * 2 / 3}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />
<animate <animate
begin={`rectangle3_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle3_${id}.begin+${dur * 2 / 3}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".14,.73,.34,1;.65,.26,.82,.45" keySplines=".14,.73,.34,1;.65,.26,.82,.45"
/> />

View File

@@ -1,21 +1,30 @@
import type { LoadingBarsProps } from "$/types/LoadingTypes"; import type { LoadingBarsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function CircleBars({ export default function CircleBars({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingBarsProps){ ariaLabel = "Loading"
}: Readonly<LoadingBarsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-rotate-fade.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-rotate-fade.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,8 +36,8 @@ export default function CircleBars({
height="5" height="5"
opacity=".14" opacity=".14"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -38,8 +47,8 @@ export default function CircleBars({
transform="rotate(30 12 12)" transform="rotate(30 12 12)"
opacity=".29" opacity=".29"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -49,8 +58,8 @@ export default function CircleBars({
transform="rotate(60 12 12)" transform="rotate(60 12 12)"
opacity=".43" opacity=".43"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -60,8 +69,8 @@ export default function CircleBars({
transform="rotate(90 12 12)" transform="rotate(90 12 12)"
opacity=".57" opacity=".57"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -71,8 +80,8 @@ export default function CircleBars({
transform="rotate(120 12 12)" transform="rotate(120 12 12)"
opacity=".71" opacity=".71"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -82,8 +91,8 @@ export default function CircleBars({
transform="rotate(150 12 12)" transform="rotate(150 12 12)"
opacity=".86" opacity=".86"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -92,14 +101,14 @@ export default function CircleBars({
height="5" height="5"
transform="rotate(180 12 12)" transform="rotate(180 12 12)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
calcMode="discrete" calcMode="discrete"
dur={animationDuration} dur={dur}
values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12" values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBarsProps } from "$/types/LoadingTypes"; import type { LoadingBarsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function FadingBars({ export default function FadingBars({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingBarsProps){ ariaLabel = "Loading"
}: Readonly<LoadingBarsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-fade.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-fade.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,14 +37,14 @@ export default function FadingBars({
height="14" height="14"
opacity="1" opacity="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_${id}`} id={`rectangle1_${id}`}
begin={`0;rectangle3_${id}.end-${animationDuration / 3}s`} begin={`0;rectangle3_${id}.end-${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />
@@ -46,13 +56,13 @@ export default function FadingBars({
height="14" height="14"
opacity=".4" opacity=".4"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 5}s`} begin={`rectangle1_${id}.begin+${dur / 5}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />
@@ -64,14 +74,14 @@ export default function FadingBars({
height="14" height="14"
opacity=".3" opacity=".3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle3_${id}`} id={`rectangle3_${id}`}
begin={`rectangle1_${id}.begin+${animationDuration * 2 / 5}s`} begin={`rectangle1_${id}.begin+${dur * 2 / 5}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBarsProps } from "$/types/LoadingTypes"; import type { LoadingBarsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function FadingGrowingBars({ export default function FadingGrowingBars({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingBarsProps){ ariaLabel = "Loading"
}: Readonly<LoadingBarsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-scale-fade.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-scale-fade.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,28 +37,28 @@ export default function FadingGrowingBars({
height="14" height="14"
opacity="1" opacity="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_${id}`} id={`rectangle1_${id}`}
begin={`0;rectangle3_${id}.end-${animationDuration / 3}s`} begin={`0;rectangle3_${id}.end-${dur / 3}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;5" values="1;5"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`0;rectangle3_${id}.end-${animationDuration / 3}s`} begin={`0;rectangle3_${id}.end-${dur / 3}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="22;14" values="22;14"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`0;rectangle3_${id}.end-${animationDuration / 3}s`} begin={`0;rectangle3_${id}.end-${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />
@@ -60,27 +70,27 @@ export default function FadingGrowingBars({
height="14" height="14"
opacity=".4" opacity=".4"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 5}s`} begin={`rectangle1_${id}.begin+${dur / 5}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;5" values="1;5"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 5}s`} begin={`rectangle1_${id}.begin+${dur / 5}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="22;14" values="22;14"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 5}s`} begin={`rectangle1_${id}.begin+${dur / 5}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />
@@ -92,28 +102,28 @@ export default function FadingGrowingBars({
height="14" height="14"
opacity=".3" opacity=".3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle3_${id}`} id={`rectangle3_${id}`}
begin={`rectangle1_${id}.begin+${animationDuration * 2 / 5}s`} begin={`rectangle1_${id}.begin+${dur * 2 / 5}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;5" values="1;5"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration * 2 / 5}s`} begin={`rectangle1_${id}.begin+${dur * 2 / 5}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="22;14" values="22;14"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration * 2 / 5}s`} begin={`rectangle1_${id}.begin+${dur * 2 / 5}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBarsProps } from "$/types/LoadingTypes"; import type { LoadingBarsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function GrowingBars({ export default function GrowingBars({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingBarsProps){ ariaLabel = "Loading"
}: Readonly<LoadingBarsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-scale.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bars-scale.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -26,23 +36,23 @@ export default function GrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_${id}`} id={`rectangle1_${id}`}
begin={`0;rectangle5_${id}.end-${animationDuration / 6}s`} begin={`0;rectangle5_${id}.end-${dur / 6}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
<animate <animate
begin={`0;rectangle5_${id}.end-${animationDuration / 6}s`} begin={`0;rectangle5_${id}.end-${dur / 6}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
@@ -53,22 +63,22 @@ export default function GrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_${id}.begin+${dur / 6}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_${id}.begin+${dur / 6}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
@@ -79,22 +89,22 @@ export default function GrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_${id}.begin+${dur / 3}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_${id}.begin+${dur / 3}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
@@ -105,22 +115,22 @@ export default function GrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_${id}.begin+${dur / 2}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_${id}.begin+${dur / 2}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
@@ -131,23 +141,23 @@ export default function GrowingBars({
width="2.8" width="2.8"
height="12" height="12"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle5_${id}`} id={`rectangle5_${id}`}
begin={`rectangle1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle1_${id}.begin+${dur * 2 / 3}s`}
attributeName="y" attributeName="y"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="6;1;6" values="6;1;6"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle1_${id}.begin+${dur * 2 / 3}s`}
attributeName="height" attributeName="height"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;22;12" values="12;22;12"
keySplines=".36,.61,.3,.98;.36,.61,.3,.98" keySplines=".36,.61,.3,.98;.36,.61,.3,.98"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBlocksProps } from "$/types/LoadingTypes"; import type { LoadingBlocksProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function PulsingBlocks({ export default function PulsingBlocks({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingBlocksProps){ ariaLabel = "Loading"
}: Readonly<LoadingBlocksProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-scale.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-scale.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,35 +37,35 @@ export default function PulsingBlocks({
width="9" width="9"
height="9" height="9"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_${id}`} id={`rectangle1_${id}`}
begin={`0;rectangle4_${id}.end+${animationDuration / 4}s`} begin={`0;rectangle4_${id}.end+${dur / 4}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1.5;.5;1.5" values="1.5;.5;1.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`0;rectangle4_${id}.end+${animationDuration / 4}s`} begin={`0;rectangle4_${id}.end+${dur / 4}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1.5;.5;1.5" values="1.5;.5;1.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`0;rectangle4_${id}.end+${animationDuration / 4}s`} begin={`0;rectangle4_${id}.end+${dur / 4}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`0;rectangle4_${id}.end+${animationDuration / 4}s`} begin={`0;rectangle4_${id}.end+${dur / 4}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
@@ -67,34 +77,34 @@ export default function PulsingBlocks({
width="9" width="9"
height="9" height="9"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 4}s`} begin={`rectangle1_${id}.begin+${dur / 4}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13.5;12.5;13.5" values="13.5;12.5;13.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 4}s`} begin={`rectangle1_${id}.begin+${dur / 4}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1.5;.5;1.5" values="1.5;.5;1.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 4}s`} begin={`rectangle1_${id}.begin+${dur / 4}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 4}s`} begin={`rectangle1_${id}.begin+${dur / 4}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
@@ -106,34 +116,34 @@ export default function PulsingBlocks({
width="9" width="9"
height="9" height="9"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_${id}.begin+${dur / 2}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13.5;12.5;13.5" values="13.5;12.5;13.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_${id}.begin+${dur / 2}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13.5;12.5;13.5" values="13.5;12.5;13.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_${id}.begin+${dur / 2}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_${id}.begin+${dur / 2}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
@@ -145,35 +155,35 @@ export default function PulsingBlocks({
width="9" width="9"
height="9" height="9"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle4_${id}`} id={`rectangle4_${id}`}
begin={`rectangle1_${id}.begin+${animationDuration * 3 / 4}s`} begin={`rectangle1_${id}.begin+${dur * 3 / 4}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1.5;.5;1.5" values="1.5;.5;1.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration * 3 / 4}s`} begin={`rectangle1_${id}.begin+${dur * 3 / 4}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13.5;12.5;13.5" values="13.5;12.5;13.5"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration * 3 / 4}s`} begin={`rectangle1_${id}.begin+${dur * 3 / 4}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />
<animate <animate
begin={`rectangle1_${id}.begin+${animationDuration * 3 / 4}s`} begin={`rectangle1_${id}.begin+${dur * 3 / 4}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="9;11;9" values="9;11;9"
keyTimes="0;.2;1" keyTimes="0;.2;1"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBlocksProps } from "$/types/LoadingTypes"; import type { LoadingBlocksProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function SlidingBlocks2({ export default function SlidingBlocks2({
size,
width, width,
height, height,
className, className,
animationDuration = 0.2, animationDuration = 200,
color,
stroke, stroke,
fill fill,
}: LoadingBlocksProps){ ariaLabel = "Loading"
}: Readonly<LoadingBlocksProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-shuffle-2.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-shuffle-2.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,14 +37,14 @@ export default function SlidingBlocks2({
width="10" width="10"
height="10" height="10"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_1_${id}`} id={`rectangle1_1_${id}`}
begin={`0;rectangle2_4_${id}.end`} begin={`0;rectangle2_4_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -42,7 +52,7 @@ export default function SlidingBlocks2({
id={`rectangle1_2_${id}`} id={`rectangle1_2_${id}`}
begin={`rectangle2_1_${id}.end`} begin={`rectangle2_1_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -50,7 +60,7 @@ export default function SlidingBlocks2({
id={`rectangle1_3_${id}`} id={`rectangle1_3_${id}`}
begin={`rectangle2_2_${id}.end`} begin={`rectangle2_2_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -58,7 +68,7 @@ export default function SlidingBlocks2({
id={`rectangle1_4_${id}`} id={`rectangle1_4_${id}`}
begin={`rectangle2_3_${id}.end`} begin={`rectangle2_3_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -70,14 +80,14 @@ export default function SlidingBlocks2({
width="10" width="10"
height="10" height="10"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle2_1_${id}`} id={`rectangle2_1_${id}`}
begin={`rectangle1_1_${id}.end`} begin={`rectangle1_1_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -85,7 +95,7 @@ export default function SlidingBlocks2({
id={`rectangle2_2_${id}`} id={`rectangle2_2_${id}`}
begin={`rectangle1_2_${id}.end`} begin={`rectangle1_2_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -93,7 +103,7 @@ export default function SlidingBlocks2({
id={`rectangle2_3_${id}`} id={`rectangle2_3_${id}`}
begin={`rectangle1_3_${id}.end`} begin={`rectangle1_3_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -101,7 +111,7 @@ export default function SlidingBlocks2({
id={`rectangle2_4_${id}`} id={`rectangle2_4_${id}`}
begin={`rectangle1_4_${id}.end`} begin={`rectangle1_4_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBlocksProps } from "$/types/LoadingTypes"; import type { LoadingBlocksProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function SlidingBlocks3({ export default function SlidingBlocks3({
size,
width, width,
height, height,
className, className,
animationDuration = 0.2, animationDuration = 200,
color,
stroke, stroke,
fill fill,
}: LoadingBlocksProps){ ariaLabel = "Loading"
}: Readonly<LoadingBlocksProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-shuffle-3.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-shuffle-3.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,14 +37,14 @@ export default function SlidingBlocks3({
width="10" width="10"
height="10" height="10"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_1_${id}`} id={`rectangle1_1_${id}`}
begin={`0;rectangle3_4_${id}.end`} begin={`0;rectangle3_4_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -42,7 +52,7 @@ export default function SlidingBlocks3({
id={`rectangle1_2_${id}`} id={`rectangle1_2_${id}`}
begin={`rectangle3_1_${id}.end`} begin={`rectangle3_1_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -50,7 +60,7 @@ export default function SlidingBlocks3({
id={`rectangle1_3_${id}`} id={`rectangle1_3_${id}`}
begin={`rectangle3_2_${id}.end`} begin={`rectangle3_2_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -58,7 +68,7 @@ export default function SlidingBlocks3({
id={`rectangle1_4_${id}`} id={`rectangle1_4_${id}`}
begin={`rectangle3_3_${id}.end`} begin={`rectangle3_3_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -70,14 +80,14 @@ export default function SlidingBlocks3({
width="10" width="10"
height="10" height="10"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle2_1_${id}`} id={`rectangle2_1_${id}`}
begin={`rectangle1_1_${id}.end`} begin={`rectangle1_1_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -85,7 +95,7 @@ export default function SlidingBlocks3({
id={`rectangle2_2_${id}`} id={`rectangle2_2_${id}`}
begin={`rectangle1_2_${id}.end`} begin={`rectangle1_2_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -93,7 +103,7 @@ export default function SlidingBlocks3({
id={`rectangle2_3_${id}`} id={`rectangle2_3_${id}`}
begin={`rectangle1_3_${id}.end`} begin={`rectangle1_3_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -101,7 +111,7 @@ export default function SlidingBlocks3({
id={`rectangle2_4_${id}`} id={`rectangle2_4_${id}`}
begin={`rectangle1_4_${id}.end`} begin={`rectangle1_4_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -113,14 +123,14 @@ export default function SlidingBlocks3({
width="10" width="10"
height="10" height="10"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle3_1_${id}`} id={`rectangle3_1_${id}`}
begin={`rectangle2_1_${id}.end`} begin={`rectangle2_1_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -128,7 +138,7 @@ export default function SlidingBlocks3({
id={`rectangle3_2_${id}`} id={`rectangle3_2_${id}`}
begin={`rectangle2_2_${id}.end`} begin={`rectangle2_2_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="13;1" values="13;1"
fill="freeze" fill="freeze"
/> />
@@ -136,7 +146,7 @@ export default function SlidingBlocks3({
id={`rectangle3_3_${id}`} id={`rectangle3_3_${id}`}
begin={`rectangle2_3_${id}.end`} begin={`rectangle2_3_${id}.end`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />
@@ -144,7 +154,7 @@ export default function SlidingBlocks3({
id={`rectangle3_4_${id}`} id={`rectangle3_4_${id}`}
begin={`rectangle2_4_${id}.end`} begin={`rectangle2_4_${id}.end`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;13" values="1;13"
fill="freeze" fill="freeze"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingBlocksProps } from "$/types/LoadingTypes"; import type { LoadingBlocksProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function WaveBlocks({ export default function WaveBlocks({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingBlocksProps){ ariaLabel = "Loading"
}: Readonly<LoadingBlocksProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-wave.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/blocks-wave.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -26,32 +36,32 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle1_1_${id}`} id={`rectangle1_1_${id}`}
begin={`0;rectangle9_1_${id}.end+${animationDuration / 3}s`} begin={`0;rectangle9_1_${id}.end+${dur / 3}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;4;1" values="1;4;1"
/> />
<animate <animate
begin={`0;rectangle9_1_${id}.end+${animationDuration / 3}s`} begin={`0;rectangle9_1_${id}.end+${dur / 3}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;4;1" values="1;4;1"
/> />
<animate <animate
begin={`0;rectangle9_1_${id}.end+${animationDuration / 3}s`} begin={`0;rectangle9_1_${id}.end+${dur / 3}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`0;rectangle9_1_${id}.end+${animationDuration / 3}s`} begin={`0;rectangle9_1_${id}.end+${dur / 3}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -61,31 +71,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="8.33;11.33;8.33" values="8.33;11.33;8.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;4;1" values="1;4;1"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -95,31 +105,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;4;1" values="1;4;1"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="8.33;11.33;8.33" values="8.33;11.33;8.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 6}s`} begin={`rectangle1_1_${id}.begin+${dur / 6}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -129,31 +139,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="15.66;18.66;15.66" values="15.66;18.66;15.66"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="1;4;1" values="1;4;1"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -163,31 +173,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="8.33;11.33;8.33" values="8.33;11.33;8.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="8.33;11.33;8.33" values="8.33;11.33;8.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -197,31 +207,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="1;4;1" values="1;4;1"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="15.66;18.66;15.66" values="15.66;18.66;15.66"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 3}s`} begin={`rectangle1_1_${id}.begin+${dur / 3}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -231,31 +241,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="15.66;18.66;15.66" values="15.66;18.66;15.66"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="8.33;11.33;8.33" values="8.33;11.33;8.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -265,31 +275,31 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="8.33;11.33;8.33" values="8.33;11.33;8.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="15.66;18.66;15.66" values="15.66;18.66;15.66"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration / 2}s`} begin={`rectangle1_1_${id}.begin+${dur / 2}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>
@@ -299,32 +309,32 @@ export default function WaveBlocks({
width="7.33" width="7.33"
height="7.33" height="7.33"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`rectangle9_1_${id}`} id={`rectangle9_1_${id}`}
begin={`rectangle1_1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle1_1_${id}.begin+${dur * 2 / 3}s`}
attributeName="x" attributeName="x"
dur={animationDuration} dur={dur}
values="15.66;18.66;15.66" values="15.66;18.66;15.66"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle1_1_${id}.begin+${dur * 2 / 3}s`}
attributeName="y" attributeName="y"
dur={animationDuration} dur={dur}
values="15.66;18.66;15.66" values="15.66;18.66;15.66"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle1_1_${id}.begin+${dur * 2 / 3}s`}
attributeName="width" attributeName="width"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
<animate <animate
begin={`rectangle1_1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`rectangle1_1_${id}.begin+${dur * 2 / 3}s`}
attributeName="height" attributeName="height"
dur={animationDuration} dur={dur}
values="7.33;1.33;7.33" values="7.33;1.33;7.33"
/> />
</rect> </rect>

View File

@@ -1,22 +1,32 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function BouncingDots({ export default function BouncingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-bounce.svg?short_path=50864c0 //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-bounce.svg?short_path=50864c0
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,15 +35,15 @@ export default function BouncingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`firstBouncingDots_${id}`} id={`firstBouncingDots_${id}`}
begin={`0;lastBouncingDots_${id}.end+${animationDuration / 2}s`} begin={`0;lastBouncingDots_${id}.end+${dur / 2}s`}
attributeName="cy" attributeName="cy"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;6;12" values="12;6;12"
keySplines=".33,.66,.66,1;.33,0,.66,.33" keySplines=".33,.66,.66,1;.33,0,.66,.33"
/> />
@@ -43,15 +53,15 @@ export default function BouncingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`secondBouncingDots_${id}`} id={`secondBouncingDots_${id}`}
begin={`firstBouncingDots_${id}.begin+${animationDuration / 5}s`} begin={`firstBouncingDots_${id}.begin+${dur / 5}s`}
attributeName="cy" attributeName="cy"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;6;12" values="12;6;12"
keySplines=".33,.66,.66,1;.33,0,.66,.33" keySplines=".33,.66,.66,1;.33,0,.66,.33"
/> />
@@ -61,15 +71,15 @@ export default function BouncingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`lastBouncingDots_${id}`} id={`lastBouncingDots_${id}`}
begin={`secondBouncingDots_${id}.begin+${animationDuration / 5}s`} begin={`secondBouncingDots_${id}.begin+${dur / 5}s`}
attributeName="cy" attributeName="cy"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="12;6;12" values="12;6;12"
keySplines=".33,.66,.66,1;.33,0,.66,.33" keySplines=".33,.66,.66,1;.33,0,.66,.33"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function CircleCenterDots({ export default function CircleCenterDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/6-dots-scale-middle.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/6-dots-scale-middle.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,15 +35,15 @@ export default function CircleCenterDots({
cy="3" cy="3"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle1_${id}`} id={`circle1_${id}`}
begin={`0;circle3_${id}.end-${animationDuration * 5 / 6}s`} begin={`0;circle3_${id}.end-${dur * 5 / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -43,15 +53,15 @@ export default function CircleCenterDots({
cy="4.21" cy="4.21"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle2_${id}`} id={`circle2_${id}`}
begin={`circle1_${id}.begin+${animationDuration / 6}s`} begin={`circle1_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -61,15 +71,15 @@ export default function CircleCenterDots({
cy="4.21" cy="4.21"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle3_${id}`} id={`circle3_${id}`}
begin={`circle5_${id}.begin+${animationDuration / 6}s`} begin={`circle5_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -79,15 +89,15 @@ export default function CircleCenterDots({
cy="7.50" cy="7.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle4_${id}`} id={`circle4_${id}`}
begin={`circle2_${id}.begin+${animationDuration / 6}s`} begin={`circle2_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -97,15 +107,15 @@ export default function CircleCenterDots({
cy="7.50" cy="7.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle5_${id}`} id={`circle5_${id}`}
begin={`circle7_${id}.begin+${animationDuration / 6}s`} begin={`circle7_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -115,14 +125,14 @@ export default function CircleCenterDots({
cy="12.00" cy="12.00"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
> >
<animate <animate
id={`circle6_${id}`} id={`circle6_${id}`}
begin={`circle4_${id}.begin+${animationDuration / 6}s`} begin={`circle4_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -132,15 +142,15 @@ export default function CircleCenterDots({
cy="12.00" cy="12.00"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle7_${id}`} id={`circle7_${id}`}
begin={`circle9_${id}.begin+${animationDuration / 6}s`} begin={`circle9_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -150,15 +160,15 @@ export default function CircleCenterDots({
cy="16.50" cy="16.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle8_${id}`} id={`circle8_${id}`}
begin={`circle6_${id}.begin+${animationDuration / 6}s`} begin={`circle6_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -168,15 +178,15 @@ export default function CircleCenterDots({
cy="16.50" cy="16.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle9_${id}`} id={`circle9_${id}`}
begin={`circle11_${id}.begin+${animationDuration / 6}s`} begin={`circle11_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -186,15 +196,15 @@ export default function CircleCenterDots({
cy="19.79" cy="19.79"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle10_${id}`} id={`circle10_${id}`}
begin={`circle8_${id}.begin+${animationDuration / 6}s`} begin={`circle8_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -204,15 +214,15 @@ export default function CircleCenterDots({
cy="19.79" cy="19.79"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle11_${id}`} id={`circle11_${id}`}
begin={`circle12_${id}.begin+${animationDuration / 6}s`} begin={`circle12_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -221,16 +231,16 @@ export default function CircleCenterDots({
cx="12" cx="12"
cy="21" cy="21"
r="0" r="0"
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
className={className} className={className}
> >
<animate <animate
id={`circle12_${id}`} id={`circle12_${id}`}
begin={`circle10_${id}.begin+${animationDuration / 6}s`} begin={`circle10_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />

View File

@@ -1,19 +1,30 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function CircleFadingDots({ export default function CircleFadingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/6-dots-rotate.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/6-dots-rotate.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -24,8 +35,8 @@ export default function CircleFadingDots({
r="1.5" r="1.5"
opacity=".14" opacity=".14"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="16.75" cx="16.75"
@@ -33,8 +44,8 @@ export default function CircleFadingDots({
r="1.5" r="1.5"
opacity=".29" opacity=".29"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="20.23" cx="20.23"
@@ -42,8 +53,8 @@ export default function CircleFadingDots({
r="1.5" r="1.5"
opacity=".43" opacity=".43"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="21.50" cx="21.50"
@@ -51,8 +62,8 @@ export default function CircleFadingDots({
r="1.5" r="1.5"
opacity=".57" opacity=".57"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="20.23" cx="20.23"
@@ -60,8 +71,8 @@ export default function CircleFadingDots({
r="1.5" r="1.5"
opacity=".71" opacity=".71"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="16.75" cx="16.75"
@@ -69,22 +80,22 @@ export default function CircleFadingDots({
r="1.5" r="1.5"
opacity=".86" opacity=".86"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="12" cx="12"
cy="21.5" cy="21.5"
r="1.5" r="1.5"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
calcMode="discrete" calcMode="discrete"
dur={animationDuration} dur={dur}
values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12;360 12 12" values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,23 +1,34 @@
import type { CirclePulsingDotsProps } from "$/types/LoadingTypes"; import type { CirclePulsingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function CirclePulsingDots({ export default function CirclePulsingDots({
size,
width, width,
height, height,
className, className,
rotationAnimationDuration = 6, rotationAnimationDuration = 6000,
growingAnimationDuration = 0.6, growingAnimationDuration = 600,
color,
stroke, stroke,
fill fill,
}: CirclePulsingDotsProps){ ariaLabel = "Loading"
}: Readonly<CirclePulsingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/12-dots-scale-rotate.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/12-dots-scale-rotate.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const rotationAnimationDur = reducedMotion ? rotationAnimationDuration / 100 : rotationAnimationDuration / 1000;
const growingAnimationDur = reducedMotion ? growingAnimationDuration / 100 : growingAnimationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -27,15 +38,15 @@ export default function CirclePulsingDots({
cy="3" cy="3"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle1_${id}`} id={`circle1_${id}`}
begin={`0;circle3_${id}.end-${growingAnimationDuration * 5 / 6}s`} begin={`0;circle3_${id}.end-${growingAnimationDur * 5 / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -45,15 +56,15 @@ export default function CirclePulsingDots({
cy="4.21" cy="4.21"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle2_${id}`} id={`circle2_${id}`}
begin={`circle1_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle1_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -63,15 +74,15 @@ export default function CirclePulsingDots({
cy="4.21" cy="4.21"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle3_${id}`} id={`circle3_${id}`}
begin={`circle5_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle5_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -81,15 +92,15 @@ export default function CirclePulsingDots({
cy="7.50" cy="7.50"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle4_${id}`} id={`circle4_${id}`}
begin={`circle2_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle2_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -99,15 +110,15 @@ export default function CirclePulsingDots({
cy="7.50" cy="7.50"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle5_${id}`} id={`circle5_${id}`}
begin={`circle7_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle7_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -117,15 +128,15 @@ export default function CirclePulsingDots({
cy="12.00" cy="12.00"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle6_${id}`} id={`circle6_${id}`}
begin={`circle4_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle4_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -135,15 +146,15 @@ export default function CirclePulsingDots({
cy="12.00" cy="12.00"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle7_${id}`} id={`circle7_${id}`}
begin={`circle9_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle9_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -153,15 +164,15 @@ export default function CirclePulsingDots({
cy="16.50" cy="16.50"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle8_${id}`} id={`circle8_${id}`}
begin={`circle6_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle6_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -171,15 +182,15 @@ export default function CirclePulsingDots({
cy="16.50" cy="16.50"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle9_${id}`} id={`circle9_${id}`}
begin={`circle11_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle11_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -189,15 +200,15 @@ export default function CirclePulsingDots({
cy="19.79" cy="19.79"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle10_${id}`} id={`circle10_${id}`}
begin={`circle8_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle8_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -207,15 +218,15 @@ export default function CirclePulsingDots({
cy="19.79" cy="19.79"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle11_${id}`} id={`circle11_${id}`}
begin={`circle12_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle12_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -225,15 +236,15 @@ export default function CirclePulsingDots({
cy="21" cy="21"
r="1" r="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`circle12_${id}`} id={`circle12_${id}`}
begin={`circle10_${id}.begin+${growingAnimationDuration / 6}s`} begin={`circle10_${id}.begin+${growingAnimationDur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={growingAnimationDuration} dur={growingAnimationDur}
values="1;2;1" values="1;2;1"
keySplines=".27,.42,.37,.99;.53,0,.61,.73" keySplines=".27,.42,.37,.99;.53,0,.61,.73"
/> />
@@ -241,7 +252,7 @@ export default function CirclePulsingDots({
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={rotationAnimationDuration} dur={rotationAnimationDur}
values="360 12 12;0 12 12" values="360 12 12;0 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,21 +1,30 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function CircleRotatingDots({ export default function CircleRotatingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 1.5, animationDuration = 1500,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/8-dots-rotate.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/8-dots-rotate.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,69 +34,69 @@ export default function CircleRotatingDots({
cy="12" cy="12"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="21" cx="21"
cy="12" cy="12"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="12" cx="12"
cy="21" cy="21"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="12" cx="12"
cy="3" cy="3"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="5.64" cx="5.64"
cy="5.64" cy="5.64"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="18.36" cx="18.36"
cy="18.36" cy="18.36"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="5.64" cx="5.64"
cy="18.36" cy="18.36"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="18.36" cx="18.36"
cy="5.64" cy="5.64"
r="2" r="2"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function CircleShrinkingDots({ export default function CircleShrinkingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/6-dots-scale.svg?short_path=17d1946 //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/6-dots-scale.svg?short_path=17d1946
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,15 +35,15 @@ export default function CircleShrinkingDots({
cy="3" cy="3"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`firstShrinkingDot_${id}`} id={`firstShrinkingDot_${id}`}
begin={`0;thirdShrinkingDot_${id}.end-${animationDuration * 4 / 5}s`} begin={`0;thirdShrinkingDot_${id}.end-${dur * 4 / 5}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -45,15 +55,15 @@ export default function CircleShrinkingDots({
cy="4.21" cy="4.21"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`secondShrinkingDot_${id}`} id={`secondShrinkingDot_${id}`}
begin={`firstShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`firstShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -65,15 +75,15 @@ export default function CircleShrinkingDots({
cy="4.21" cy="4.21"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`thirdShrinkingDot_${id}`} id={`thirdShrinkingDot_${id}`}
begin={`fifthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`fifthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -85,15 +95,15 @@ export default function CircleShrinkingDots({
cy="7.50" cy="7.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`fourthShrinkingDot_${id}`} id={`fourthShrinkingDot_${id}`}
begin={`secondShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`secondShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" fill="freeze" keySplines="0,1,0,1;.53,0,.61,.73" fill="freeze"
@@ -104,15 +114,15 @@ export default function CircleShrinkingDots({
cy="7.50" cy="7.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`fifthShrinkingDot_${id}`} id={`fifthShrinkingDot_${id}`}
begin={`seventhShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`seventhShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -124,15 +134,15 @@ export default function CircleShrinkingDots({
cy="12.00" cy="12.00"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`sixthShrinkingDot_${id}`} id={`sixthShrinkingDot_${id}`}
begin={`fourthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`fourthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -144,15 +154,15 @@ export default function CircleShrinkingDots({
cy="12.00" cy="12.00"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`seventhShrinkingDot_${id}`} id={`seventhShrinkingDot_${id}`}
begin={`ninthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`ninthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -164,15 +174,15 @@ export default function CircleShrinkingDots({
cy="16.50" cy="16.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`eighthShrinkingDot_${id}`} id={`eighthShrinkingDot_${id}`}
begin={`sixthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`sixthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -184,15 +194,15 @@ export default function CircleShrinkingDots({
cy="16.50" cy="16.50"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`ninthShrinkingDot_${id}`} id={`ninthShrinkingDot_${id}`}
begin={`eleventhShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`eleventhShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -204,15 +214,15 @@ export default function CircleShrinkingDots({
cy="19.79" cy="19.79"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`tenthShrinkingDot_${id}`} id={`tenthShrinkingDot_${id}`}
begin={`eighthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`eighthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -224,15 +234,15 @@ export default function CircleShrinkingDots({
cy="19.79" cy="19.79"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`eleventhShrinkingDot_${id}`} id={`eleventhShrinkingDot_${id}`}
begin={`twelfthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`twelfthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"
@@ -244,15 +254,15 @@ export default function CircleShrinkingDots({
cy="21" cy="21"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`twelfthShrinkingDot_${id}`} id={`twelfthShrinkingDot_${id}`}
begin={`tenthShrinkingDot_${id}.begin+${animationDuration / 6}s`} begin={`tenthShrinkingDot_${id}.begin+${dur / 6}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;2;0" values="0;2;0"
keyTimes="0;.2;1" keyTimes="0;.2;1"
keySplines="0,1,0,1;.53,0,.61,.73" keySplines="0,1,0,1;.53,0,.61,.73"

View File

@@ -1,21 +1,33 @@
import type { CircleSpinningDotProps } from "$/types/LoadingTypes"; import type { CircleSpinningDotProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function CircleSpinningDot({ export default function CircleSpinningDot({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill, fill,
trackClassName = "fill-transparent", trackClassName = "fill-transparent",
trackStroke, trackStroke,
trackFill trackFill,
}: CircleSpinningDotProps){ ariaLabel = "Loading"
}: Readonly<CircleSpinningDotProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/dot-revolve.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/dot-revolve.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -30,13 +42,13 @@ export default function CircleSpinningDot({
cy="2.5" cy="2.5"
r="1.5" r="1.5"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,21 +1,32 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function CyclingDots({ export default function CyclingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.5, animationDuration = 500,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-move.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-move.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -24,14 +35,14 @@ export default function CyclingDots({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin="0;spinner_z0Or.end" begin="0;spinner_z0Or.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="0;3" values="0;3"
fill="freeze" fill="freeze"
@@ -40,7 +51,7 @@ export default function CyclingDots({
begin="spinner_OLMs.end" begin="spinner_OLMs.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="4;12" values="4;12"
fill="freeze" fill="freeze"
@@ -49,7 +60,7 @@ export default function CyclingDots({
begin="spinner_UHR2.end" begin="spinner_UHR2.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="12;20" values="12;20"
fill="freeze" fill="freeze"
@@ -59,7 +70,7 @@ export default function CyclingDots({
begin="spinner_Aguh.end" begin="spinner_Aguh.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="3;0" values="3;0"
fill="freeze" fill="freeze"
@@ -78,14 +89,14 @@ export default function CyclingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin="0;spinner_z0Or.end" begin="0;spinner_z0Or.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="4;12" values="4;12"
fill="freeze" fill="freeze"
@@ -94,7 +105,7 @@ export default function CyclingDots({
begin="spinner_OLMs.end" begin="spinner_OLMs.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="12;20" values="12;20"
fill="freeze" fill="freeze"
@@ -104,7 +115,7 @@ export default function CyclingDots({
begin="spinner_UHR2.end" begin="spinner_UHR2.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="3;0" values="3;0"
fill="freeze" fill="freeze"
@@ -121,7 +132,7 @@ export default function CyclingDots({
begin="spinner_Aguh.end" begin="spinner_Aguh.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="0;3" values="0;3"
fill="freeze" fill="freeze"
@@ -132,14 +143,14 @@ export default function CyclingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin="0;spinner_z0Or.end" begin="0;spinner_z0Or.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="12;20" values="12;20"
fill="freeze" fill="freeze"
@@ -149,7 +160,7 @@ export default function CyclingDots({
begin="spinner_OLMs.end" begin="spinner_OLMs.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="3;0" values="3;0"
fill="freeze" fill="freeze"
@@ -166,7 +177,7 @@ export default function CyclingDots({
begin="spinner_UHR2.end" begin="spinner_UHR2.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="0;3" values="0;3"
fill="freeze" fill="freeze"
@@ -175,7 +186,7 @@ export default function CyclingDots({
begin="spinner_Aguh.end" begin="spinner_Aguh.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="4;12" values="4;12"
fill="freeze" fill="freeze"
@@ -186,15 +197,15 @@ export default function CyclingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`fourthDotFirstAnimate_${id}`} id={`fourthDotFirstAnimate_${id}`}
begin="0;spinner_z0Or.end" begin="0;spinner_z0Or.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="3;0" values="3;0"
fill="freeze" fill="freeze"
@@ -211,7 +222,7 @@ export default function CyclingDots({
begin="spinner_OLMs.end" begin="spinner_OLMs.end"
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="0;3" values="0;3"
fill="freeze" fill="freeze"
@@ -220,7 +231,7 @@ export default function CyclingDots({
begin="spinner_UHR2.end" begin="spinner_UHR2.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="4;12" values="4;12"
fill="freeze" fill="freeze"
@@ -229,7 +240,7 @@ export default function CyclingDots({
begin="spinner_Aguh.end" begin="spinner_Aguh.end"
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1" keySplines=".36,.6,.31,1"
values="12;20" values="12;20"
fill="freeze" fill="freeze"

View File

@@ -1,22 +1,32 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function FadingDots({ export default function FadingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-fade.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-fade.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -26,14 +36,14 @@ export default function FadingDots({
r="3" r="3"
opacity="1" opacity="1"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`firstFadingDots_${id}`} id={`firstFadingDots_${id}`}
begin={`0;lastFadingDots_${id}.end-${animationDuration / 3}s`} begin={`0;lastFadingDots_${id}.end-${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />
@@ -44,14 +54,14 @@ export default function FadingDots({
r="3" r="3"
opacity=".4" opacity=".4"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`secondFadingDots_${id}`} id={`secondFadingDots_${id}`}
begin={`firstFadingDots_${id}.begin+${animationDuration / 5}s`} begin={`firstFadingDots_${id}.begin+${dur / 5}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />
@@ -62,14 +72,14 @@ export default function FadingDots({
r="3" r="3"
opacity=".3" opacity=".3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`lastFadingDots_${id}`} id={`lastFadingDots_${id}`}
begin={`secondFadingDots_${id}.begin+${animationDuration / 5}s`} begin={`secondFadingDots_${id}.begin+${dur / 5}s`}
attributeName="opacity" attributeName="opacity"
dur={animationDuration} dur={dur}
values="1;.2" values="1;.2"
fill="freeze" fill="freeze"
/> />

View File

@@ -1,19 +1,30 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function PulsingDots({ export default function PulsingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-scale-middle.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-scale-middle.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -22,12 +33,12 @@ export default function PulsingDots({
cy="12" cy="12"
r="1.5" r="1.5"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="r" attributeName="r"
dur={animationDuration} dur={dur}
values="1.5;3;1.5" values="1.5;3;1.5"
repeatCount="indefinite" repeatCount="indefinite"
/> />
@@ -37,12 +48,12 @@ export default function PulsingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="r" attributeName="r"
dur={animationDuration} dur={dur}
values="3;1.5;3" values="3;1.5;3"
repeatCount="indefinite" repeatCount="indefinite"
/> />
@@ -52,12 +63,12 @@ export default function PulsingDots({
cy="12" cy="12"
r="1.5" r="1.5"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="r" attributeName="r"
dur={animationDuration} dur={dur}
values="1.5;3;1.5" values="1.5;3;1.5"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,18 +1,29 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function RotatingDots({ export default function RotatingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 1, animationDuration = 1000,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-rotate.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-rotate.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -21,8 +32,8 @@ export default function RotatingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<g> <g>
<circle <circle
@@ -30,22 +41,22 @@ export default function RotatingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<circle <circle
cx="20" cx="20"
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
keySplines=".36,.6,.31,1;.36,.6,.31,1" keySplines=".36,.6,.31,1;.36,.6,.31,1"
values="0 12 12;180 12 12;360 12 12" values="0 12 12;180 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"

View File

@@ -1,22 +1,32 @@
import type { LoadingDotsProps } from "$/types/LoadingTypes"; import type { LoadingDotsProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function SwellingDots({ export default function SwellingDots({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingDotsProps){ ariaLabel = "Loading"
}: Readonly<LoadingDotsProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-scale.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/3-dots-scale.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,14 +35,14 @@ export default function SwellingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`firstDot_${id}`} id={`firstDot_${id}`}
begin={`0;lastDot_${id}.end-${animationDuration / 3}s`} begin={`0;lastDot_${id}.end-${dur / 3}s`}
attributeName="r" attributeName="r"
dur={animationDuration} dur={dur}
values="3;.2;3" values="3;.2;3"
/> />
</circle> </circle>
@@ -41,13 +51,13 @@ export default function SwellingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
begin={`firstDot_${id}.end-${animationDuration * 4 / 5}s`} begin={`firstDot_${id}.end-${dur * 4 / 5}s`}
attributeName="r" attributeName="r"
dur={animationDuration} dur={dur}
values="3;.2;3" values="3;.2;3"
/> />
</circle> </circle>
@@ -56,14 +66,14 @@ export default function SwellingDots({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`lastDot_${id}`} id={`lastDot_${id}`}
begin={`firstDot_${id}.end-${animationDuration * 2 / 3}s`} begin={`firstDot_${id}.end-${dur * 2 / 3}s`}
attributeName="r" attributeName="r"
dur={animationDuration} dur={dur}
values="3;.2;3" values="3;.2;3"
/> />
</circle> </circle>

View File

@@ -1,22 +1,32 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function DoubleDrop({ export default function DoubleDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-2.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-2.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,24 +35,24 @@ export default function DoubleDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop1_${id}`} id={`drop1_${id}`}
begin={`0;drop2_${id}.begin+${animationDuration / 2}s`} begin={`0;drop2_${id}.begin+${dur / 2}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`0;drop2_${id}.begin+${animationDuration / 2}s`} begin={`0;drop2_${id}.begin+${dur / 2}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
@@ -53,24 +63,24 @@ export default function DoubleDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop2_${id}`} id={`drop2_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 2}s`} begin={`drop1_${id}.begin+${dur / 2}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 2}s`} begin={`drop1_${id}.begin+${dur / 2}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"

View File

@@ -1,22 +1,32 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function DoubleWaveDrop({ export default function DoubleWaveDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-rings-2.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-rings-2.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -24,34 +34,34 @@ export default function DoubleWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop1_${id}`} id={`drop1_${id}`}
begin={`0;drop2_${id}.begin+${animationDuration / 2}s`} begin={`0;drop2_${id}.begin+${dur / 2}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`0;drop2_${id}.begin+${animationDuration / 2}s`} begin={`0;drop2_${id}.begin+${dur / 2}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`0;drop2_${id}.begin+${animationDuration / 2}s`} begin={`0;drop2_${id}.begin+${dur / 2}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -60,34 +70,34 @@ export default function DoubleWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop2_${id}`} id={`drop2_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 2}s`} begin={`drop1_${id}.begin+${dur / 2}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`drop1_${id}.begin+${animationDuration / 2}s`} begin={`drop1_${id}.begin+${dur / 2}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 2}s`} begin={`drop1_${id}.begin+${dur / 2}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />

View File

@@ -1,21 +1,30 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function Drop({ export default function Drop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -24,13 +33,13 @@ export default function Drop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
repeatCount="indefinite" repeatCount="indefinite"
@@ -38,7 +47,7 @@ export default function Drop({
<animate <animate
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
repeatCount="indefinite" repeatCount="indefinite"

View File

@@ -1,22 +1,32 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function QuickDrop({ export default function QuickDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-multiple.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-multiple.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,15 +35,15 @@ export default function QuickDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop1_${id}`} id={`drop1_${id}`}
begin={`0;drop3_${id}.end`} begin={`0;drop3_${id}.end`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
@@ -42,7 +52,7 @@ export default function QuickDrop({
begin={`0;drop3_${id}.end`} begin={`0;drop3_${id}.end`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
@@ -53,24 +63,24 @@ export default function QuickDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop2_${id}`} id={`drop2_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 6}`} begin={`drop1_${id}.begin+${dur / 6}`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 6}`} begin={`drop1_${id}.begin+${dur / 6}`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
@@ -81,24 +91,24 @@ export default function QuickDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop3_${id}`} id={`drop3_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 3}`} begin={`drop1_${id}.begin+${dur / 3}`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 3}`} begin={`drop1_${id}.begin+${dur / 3}`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"

View File

@@ -1,22 +1,32 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function QuickWaveDrop({ export default function QuickWaveDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-rings-multiple.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-rings-multiple.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -24,8 +34,8 @@ export default function QuickWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop1_${id}`} id={`drop1_${id}`}
@@ -33,7 +43,7 @@ export default function QuickWaveDrop({
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -43,7 +53,7 @@ export default function QuickWaveDrop({
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -51,7 +61,7 @@ export default function QuickWaveDrop({
begin={`0;drop3_${id}.end`} begin={`0;drop3_${id}.end`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -60,34 +70,34 @@ export default function QuickWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop2_${id}`} id={`drop2_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 6}s`} begin={`drop1_${id}.begin+${dur / 6}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`drop1_${id}.begin+${animationDuration / 6}s`} begin={`drop1_${id}.begin+${dur / 6}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 6}s`} begin={`drop1_${id}.begin+${dur / 6}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -96,34 +106,34 @@ export default function QuickWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop3_${id}`} id={`drop3_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function TripleDrop({ export default function TripleDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-3.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-3.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -25,24 +35,24 @@ export default function TripleDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop1_${id}`} id={`drop1_${id}`}
begin={`0;drop3_${id}.begin+${animationDuration / 3}s`} begin={`0;drop3_${id}.begin+${dur / 3}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`0;drop3_${id}.begin+${animationDuration / 3}s`} begin={`0;drop3_${id}.begin+${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
@@ -53,24 +63,24 @@ export default function TripleDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop2_${id}`} id={`drop2_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
@@ -81,24 +91,24 @@ export default function TripleDrop({
cy="12" cy="12"
r="0" r="0"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`drop3_${id}`} id={`drop3_${id}`}
begin={`drop1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`drop1_${id}.begin+${dur * 2 / 3}s`}
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="0;11" values="0;11"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`drop1_${id}.begin+${dur * 2 / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
fill="freeze" fill="freeze"

View File

@@ -1,22 +1,32 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function TripleWaveDrop({ export default function TripleWaveDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-rings-3.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-rings-3.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -24,34 +34,34 @@ export default function TripleWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop1_${id}`} id={`drop1_${id}`}
begin={`0;drop3_${id}.begin+${animationDuration / 3}s`} begin={`0;drop3_${id}.begin+${dur / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`0;drop3_${id}.begin+${animationDuration / 3}s`} begin={`0;drop3_${id}.begin+${dur / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`0;drop3_${id}.begin+${animationDuration / 3}s`} begin={`0;drop3_${id}.begin+${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -60,34 +70,34 @@ export default function TripleWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop2_${id}`} id={`drop2_${id}`}
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration / 3}s`} begin={`drop1_${id}.begin+${dur / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
@@ -96,34 +106,34 @@ export default function TripleWaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
id={`drop3_${id}`} id={`drop3_${id}`}
begin={`drop1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`drop1_${id}.begin+${dur * 2 / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animateTransform <animateTransform
begin={`drop1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`drop1_${id}.begin+${dur * 2 / 3}s`}
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />
<animate <animate
begin={`drop1_${id}.begin+${animationDuration * 2 / 3}s`} begin={`drop1_${id}.begin+${dur * 2 / 3}s`}
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
/> />

View File

@@ -1,21 +1,30 @@
import type { LoadingPulseProps } from "$/types/LoadingTypes"; import type { LoadingPulseProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function WaveDrop({ export default function WaveDrop({
size,
width, width,
height, height,
className, className,
animationDuration = 1.2, animationDuration = 1200,
color,
stroke, stroke,
fill fill,
}: LoadingPulseProps){ ariaLabel = "Loading"
}: Readonly<LoadingPulseProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-ring.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/pulse-ring.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -23,14 +32,14 @@ export default function WaveDrop({
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
transform="translate(12, 12) scale(0)" transform="translate(12, 12) scale(0)"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
calcMode="spline" calcMode="spline"
type="translate" type="translate"
dur={animationDuration} dur={dur}
values="12 12;0 0" values="12 12;0 0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
repeatCount="indefinite" repeatCount="indefinite"
@@ -40,7 +49,7 @@ export default function WaveDrop({
calcMode="spline" calcMode="spline"
additive="sum" additive="sum"
type="scale" type="scale"
dur={animationDuration} dur={dur}
values="0;1" values="0;1"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
repeatCount="indefinite" repeatCount="indefinite"
@@ -48,7 +57,7 @@ export default function WaveDrop({
<animate <animate
attributeName="opacity" attributeName="opacity"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="1;0" values="1;0"
keySplines=".52,.6,.25,.99" keySplines=".52,.6,.25,.99"
repeatCount="indefinite" repeatCount="indefinite"

View File

@@ -1,22 +1,33 @@
import type { LoadingSpinnerProps } from "$/types/LoadingTypes"; import type { LoadingSpinnerProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function HalfSpinner({ export default function HalfSpinner({
size,
width, width,
height, height,
animationDuration = 1, animationDuration = 1000,
color,
className, className,
stroke, stroke,
fill, fill,
trackClassName = "fill-transparent", trackClassName = "fill-transparent",
trackStroke, trackStroke,
trackFill trackFill,
}: LoadingSpinnerProps){ ariaLabel = "Loading"
}: Readonly<LoadingSpinnerProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/180-ring-with-bg.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/180-ring-with-bg.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -29,13 +40,13 @@ export default function HalfSpinner({
<path <path
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,22 +1,33 @@
import type { LoadingSpinnerProps } from "$/types/LoadingTypes"; import type { LoadingSpinnerProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function QuarterSpinner({ export default function QuarterSpinner({
size,
width, width,
height, height,
animationDuration = 1, animationDuration = 1000,
color,
className, className,
stroke, stroke,
fill, fill,
trackClassName = "fill-transparent", trackClassName = "fill-transparent",
trackStroke, trackStroke,
trackFill trackFill,
}: LoadingSpinnerProps){ ariaLabel = "Loading"
}: Readonly<LoadingSpinnerProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/90-ring-with-bg.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/90-ring-with-bg.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -29,13 +40,13 @@ export default function QuarterSpinner({
<path <path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,23 +1,35 @@
import type { RubberLoadingSpinnerProps } from "$/types/LoadingTypes"; import type { RubberLoadingSpinnerProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function RubberSpinner({ export default function RubberSpinner({
size,
width, width,
height, height,
animationDuration = 2, animationDuration = 2000,
stretchDuration = 1.5, stretchDuration = 1500,
color,
className, className,
stroke, stroke,
fill = "none", fill = "none",
trackClassName = "fill-transparent", trackClassName = "fill-transparent",
trackStroke, trackStroke,
trackFill trackFill,
}: RubberLoadingSpinnerProps){ ariaLabel = "Loading"
}: Readonly<RubberLoadingSpinnerProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/ring-resize.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/ring-resize.svg
const reducedMotion = usePrefersReducedMotion();
const animationDur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
const stretchDur = reducedMotion ? stretchDuration / 100 : stretchDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -33,14 +45,14 @@ export default function RubberSpinner({
cy="12" cy="12"
r="9.5" r="9.5"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
strokeWidth="3" strokeWidth="3"
strokeLinecap="round" strokeLinecap="round"
> >
<animate <animate
attributeName="stroke-dasharray" attributeName="stroke-dasharray"
dur={stretchDuration} dur={stretchDur}
calcMode="spline" calcMode="spline"
values="0 150;42 150;42 150;42 150" values="0 150;42 150;42 150;42 150"
keyTimes="0;0.475;0.95;1" keyTimes="0;0.475;0.95;1"
@@ -49,7 +61,7 @@ export default function RubberSpinner({
/> />
<animate <animate
attributeName="stroke-dashoffset" attributeName="stroke-dashoffset"
dur={stretchDuration} dur={stretchDur}
calcMode="spline" calcMode="spline"
values="0;-16;-59;-59" values="0;-16;-59;-59"
keyTimes="0;0.475;0.95;1" keyTimes="0;0.475;0.95;1"
@@ -60,7 +72,7 @@ export default function RubberSpinner({
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={animationDur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,22 +1,33 @@
import type { LoadingSpinnerProps } from "$/types/LoadingTypes"; import type { LoadingSpinnerProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function ThreeQuarterSpinner({ export default function ThreeQuarterSpinner({
size,
width, width,
height, height,
animationDuration = 1, animationDuration = 1000,
color,
className, className,
stroke, stroke,
fill, fill,
trackClassName = "fill-transparent", trackClassName = "fill-transparent",
trackStroke, trackStroke,
trackFill trackFill,
}: LoadingSpinnerProps){ ariaLabel = "Loading"
}: Readonly<LoadingSpinnerProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/270-ring-with-bg.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/270-ring-with-bg.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -29,13 +40,13 @@ export default function ThreeQuarterSpinner({
<path <path
d="M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z" d="M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,22 +1,32 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function BouncingDot({ export default function BouncingDot({
size,
width, width,
height, height,
className, className,
animationDuration = 0.9, animationDuration = 900,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bouncing-ball.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/bouncing-ball.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -26,15 +36,15 @@ export default function BouncingDot({
rx="4" rx="4"
ry="4" ry="4"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
id={`dot1_1_${id}`} id={`dot1_1_${id}`}
begin={`0;dot1_5_${id}.end`} begin={`0;dot1_5_${id}.end`}
attributeName="cy" attributeName="cy"
calcMode="spline" calcMode="spline"
dur={animationDuration * 5 / 12} dur={dur * 5 / 12}
values="5;20" values="5;20"
keySplines=".33,0,.66,.33" keySplines=".33,0,.66,.33"
fill="freeze" fill="freeze"
@@ -43,7 +53,7 @@ export default function BouncingDot({
begin={`dot1_1_${id}.end`} begin={`dot1_1_${id}.end`}
attributeName="rx" attributeName="rx"
calcMode="spline" calcMode="spline"
dur={animationDuration / 18} dur={dur / 18}
values="4;4.8;4" values="4;4.8;4"
keySplines=".33,0,.66,.33;.33,.66,.66,1" keySplines=".33,0,.66,.33;.33,.66,.66,1"
/> />
@@ -51,7 +61,7 @@ export default function BouncingDot({
begin={`dot1_1_${id}.end`} begin={`dot1_1_${id}.end`}
attributeName="ry" attributeName="ry"
calcMode="spline" calcMode="spline"
dur={animationDuration / 18} dur={dur / 18}
values="4;3;4" values="4;3;4"
keySplines=".33,0,.66,.33;.33,.66,.66,1" keySplines=".33,0,.66,.33;.33,.66,.66,1"
/> />
@@ -60,7 +70,7 @@ export default function BouncingDot({
begin={`dot1_1_${id}.end`} begin={`dot1_1_${id}.end`}
attributeName="cy" attributeName="cy"
calcMode="spline" calcMode="spline"
dur={animationDuration / 36} dur={dur / 36}
values="20;20.5" values="20;20.5"
keySplines=".33,0,.66,.33" keySplines=".33,0,.66,.33"
/> />
@@ -69,7 +79,7 @@ export default function BouncingDot({
begin={`dot1_4_${id}.end`} begin={`dot1_4_${id}.end`}
attributeName="cy" attributeName="cy"
calcMode="spline" calcMode="spline"
dur={animationDuration * 4 / 9} dur={dur * 4 / 9}
values="20.5;5" values="20.5;5"
keySplines=".33,.66,.66,1" keySplines=".33,.66,.66,1"
/> />

View File

@@ -1,21 +1,30 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function PulsingLine({ export default function PulsingLine({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/gooey-balls-1.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/gooey-balls-1.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -44,13 +53,13 @@ export default function PulsingLine({
cy="12" cy="12"
r="3" r="3"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="4;9;4" values="4;9;4"
keySplines=".56,.52,.17,.98;.56,.52,.17,.98" keySplines=".56,.52,.17,.98;.56,.52,.17,.98"
repeatCount="indefinite" repeatCount="indefinite"
@@ -58,7 +67,7 @@ export default function PulsingLine({
<animate <animate
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="3;8;3" values="3;8;3"
keySplines=".56,.52,.17,.98;.56,.52,.17,.98" keySplines=".56,.52,.17,.98;.56,.52,.17,.98"
repeatCount="indefinite" repeatCount="indefinite"
@@ -69,13 +78,13 @@ export default function PulsingLine({
cy="12" cy="12"
r="8" r="8"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="15;20;15" values="15;20;15"
keySplines=".56,.52,.17,.98;.56,.52,.17,.98" keySplines=".56,.52,.17,.98;.56,.52,.17,.98"
repeatCount="indefinite" repeatCount="indefinite"
@@ -83,7 +92,7 @@ export default function PulsingLine({
<animate <animate
attributeName="r" attributeName="r"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="8;3;8" values="8;3;8"
keySplines=".56,.52,.17,.98;.56,.52,.17,.98" keySplines=".56,.52,.17,.98;.56,.52,.17,.98"
repeatCount="indefinite" repeatCount="indefinite"

View File

@@ -1,22 +1,32 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
import { useId } from "react";
export default function SpinningBinary({ export default function SpinningBinary({
size,
width, width,
height, height,
className, className,
animationDuration = 2, animationDuration = 2000,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/gooey-balls-2.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/gooey-balls-2.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@@ -49,13 +59,13 @@ export default function SpinningBinary({
cy="12" cy="12"
r="4" r="4"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="5;8;5" values="5;8;5"
keySplines=".36,.62,.43,.99;.79,0,.58,.57" keySplines=".36,.62,.43,.99;.79,0,.58,.57"
repeatCount="indefinite" repeatCount="indefinite"
@@ -66,13 +76,13 @@ export default function SpinningBinary({
cy="12" cy="12"
r="4" r="4"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animate <animate
attributeName="cx" attributeName="cx"
calcMode="spline" calcMode="spline"
dur={animationDuration} dur={dur}
values="19;16;19" values="19;16;19"
keySplines=".36,.62,.43,.99;.79,0,.58,.57" keySplines=".36,.62,.43,.99;.79,0,.58,.57"
repeatCount="indefinite" repeatCount="indefinite"
@@ -81,7 +91,7 @@ export default function SpinningBinary({
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration * 3 / 8} dur={dur * 3 / 8}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,29 +1,38 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function SpinningClock({ export default function SpinningClock({
size,
width, width,
height, height,
className, className,
animationDuration = 9, animationDuration = 9000,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/clock.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/clock.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
/> />
<rect <rect
x="11" x="11"
@@ -32,13 +41,13 @@ export default function SpinningClock({
width="2" width="2"
height="9" height="9"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration / 12} dur={dur / 12}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />
@@ -54,7 +63,7 @@ export default function SpinningClock({
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,34 +1,43 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function SpinningEclipse({ export default function SpinningEclipse({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/eclipse.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/eclipse.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z" d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,34 +1,43 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function SpinningEclipseHalf({ export default function SpinningEclipseHalf({
size,
width, width,
height, height,
className, className,
animationDuration = 0.6, animationDuration = 600,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/eclipse-half.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/eclipse-half.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M2,12A10.94,10.94,0,0,1,5,4.65c-.21-.19-.42-.36-.62-.55h0A11,11,0,0,0,12,23c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z" d="M2,12A10.94,10.94,0,0,1,5,4.65c-.21-.19-.42-.36-.62-.55h0A11,11,0,0,0,12,23c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,34 +1,43 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function SpinningGalaxy({ export default function SpinningGalaxy({
size,
width, width,
height, height,
className, className,
animationDuration = 1.5, animationDuration = 1500,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/wind-toy.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/wind-toy.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M20.27,4.74a4.93,4.93,0,0,1,1.52,4.61,5.32,5.32,0,0,1-4.1,4.51,5.12,5.12,0,0,1-5.2-1.5,5.53,5.53,0,0,0,6.13-1.48A5.66,5.66,0,0,0,20.27,4.74ZM12.32,11.53a5.49,5.49,0,0,0-1.47-6.2A5.57,5.57,0,0,0,4.71,3.72,5.17,5.17,0,0,1,9.53,2.2,5.52,5.52,0,0,1,13.9,6.45,5.28,5.28,0,0,1,12.32,11.53ZM19.2,20.29a4.92,4.92,0,0,1-4.72,1.49,5.32,5.32,0,0,1-4.34-4.05A5.2,5.2,0,0,1,11.6,12.5a5.6,5.6,0,0,0,1.51,6.13A5.63,5.63,0,0,0,19.2,20.29ZM3.79,19.38A5.18,5.18,0,0,1,2.32,14a5.3,5.3,0,0,1,4.59-4,5,5,0,0,1,4.58,1.61,5.55,5.55,0,0,0-6.32,1.69A5.46,5.46,0,0,0,3.79,19.38ZM12.23,12a5.11,5.11,0,0,0,3.66-5,5.75,5.75,0,0,0-3.18-6,5,5,0,0,1,4.42,2.3,5.21,5.21,0,0,1,.24,5.92A5.4,5.4,0,0,1,12.23,12ZM11.76,12a5.18,5.18,0,0,0-3.68,5.09,5.58,5.58,0,0,0,3.19,5.79c-1,.35-2.9-.46-4-1.68A5.51,5.51,0,0,1,11.76,12ZM23,12.63a5.07,5.07,0,0,1-2.35,4.52,5.23,5.23,0,0,1-5.91.2,5.24,5.24,0,0,1-2.67-4.77,5.51,5.51,0,0,0,5.45,3.33A5.52,5.52,0,0,0,23,12.63ZM1,11.23a5,5,0,0,1,2.49-4.5,5.23,5.23,0,0,1,5.81-.06,5.3,5.3,0,0,1,2.61,4.74A5.56,5.56,0,0,0,6.56,8.06,5.71,5.71,0,0,0,1,11.23Z" d="M20.27,4.74a4.93,4.93,0,0,1,1.52,4.61,5.32,5.32,0,0,1-4.1,4.51,5.12,5.12,0,0,1-5.2-1.5,5.53,5.53,0,0,0,6.13-1.48A5.66,5.66,0,0,0,20.27,4.74ZM12.32,11.53a5.49,5.49,0,0,0-1.47-6.2A5.57,5.57,0,0,0,4.71,3.72,5.17,5.17,0,0,1,9.53,2.2,5.52,5.52,0,0,1,13.9,6.45,5.28,5.28,0,0,1,12.32,11.53ZM19.2,20.29a4.92,4.92,0,0,1-4.72,1.49,5.32,5.32,0,0,1-4.34-4.05A5.2,5.2,0,0,1,11.6,12.5a5.6,5.6,0,0,0,1.51,6.13A5.63,5.63,0,0,0,19.2,20.29ZM3.79,19.38A5.18,5.18,0,0,1,2.32,14a5.3,5.3,0,0,1,4.59-4,5,5,0,0,1,4.58,1.61,5.55,5.55,0,0,0-6.32,1.69A5.46,5.46,0,0,0,3.79,19.38ZM12.23,12a5.11,5.11,0,0,0,3.66-5,5.75,5.75,0,0,0-3.18-6,5,5,0,0,1,4.42,2.3,5.21,5.21,0,0,1,.24,5.92A5.4,5.4,0,0,1,12.23,12ZM11.76,12a5.18,5.18,0,0,0-3.68,5.09,5.58,5.58,0,0,0,3.19,5.79c-1,.35-2.9-.46-4-1.68A5.51,5.51,0,0,1,11.76,12ZM23,12.63a5.07,5.07,0,0,1-2.35,4.52,5.23,5.23,0,0,1-5.91.2,5.24,5.24,0,0,1-2.67-4.77,5.51,5.51,0,0,0,5.45,3.33A5.52,5.52,0,0,0,23,12.63ZM1,11.23a5,5,0,0,1,2.49-4.5,5.23,5.23,0,0,1,5.81-.06,5.3,5.3,0,0,1,2.61,4.74A5.56,5.56,0,0,0,6.56,8.06,5.71,5.71,0,0,0,1,11.23Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,34 +1,43 @@
import type { LoadingVariousProps } from "$/types/LoadingTypes"; import type { LoadingVariousProps } from "$/types/LoadingTypes";
import { usePrefersReducedMotion } from "$/util/AccessibilityUtil";
export default function SpinningTadpole({ export default function SpinningTadpole({
size,
width, width,
height, height,
className, className,
animationDuration = 0.75, animationDuration = 750,
color,
stroke, stroke,
fill fill,
}: LoadingVariousProps){ ariaLabel = "Loading"
}: Readonly<LoadingVariousProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/tadpole.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/tadpole.svg
const reducedMotion = usePrefersReducedMotion();
const dur = reducedMotion ? animationDuration / 100 : animationDuration / 1000;
return ( return (
<svg <svg
width={width} width={size ?? width}
height={height} height={size ?? height}
role="status"
aria-live="polite"
aria-label={ariaLabel}
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M12,23a9.63,9.63,0,0,1-8-9.5,9.51,9.51,0,0,1,6.79-9.1A1.66,1.66,0,0,0,12,2.81h0a1.67,1.67,0,0,0-1.94-1.64A11,11,0,0,0,12,23Z" d="M12,23a9.63,9.63,0,0,1-8-9.5,9.51,9.51,0,0,1,6.79-9.1A1.66,1.66,0,0,0,12,2.81h0a1.67,1.67,0,0,0-1.94-1.64A11,11,0,0,0,12,23Z"
className={className} className={className}
stroke={stroke} stroke={color ?? stroke}
fill={fill} fill={color ?? fill}
> >
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
dur={animationDuration} dur={dur}
values="0 12 12;360 12 12" values="0 12 12;360 12 12"
repeatCount="indefinite" repeatCount="indefinite"
/> />

View File

@@ -1,4 +1,5 @@
import type { LoadingWifiProps } from "$/types/LoadingTypes"; import type { LoadingWifiProps } from "$/types/LoadingTypes";
import { useId } from "react";
export default function Wifi({ export default function Wifi({
@@ -9,9 +10,9 @@ export default function Wifi({
fadeDuration = 0.001, fadeDuration = 0.001,
stroke, stroke,
fill fill
}: LoadingWifiProps){ }: Readonly<LoadingWifiProps>){
//https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/wifi.svg //https://github.com/n3r4zzurr0/svg-spinners/blob/main/svg-smil/wifi.svg
const id = crypto.randomUUID().replaceAll("-", ""); const id = useId();
return ( return (

View File

@@ -3,17 +3,14 @@ import { Dialog, DialogBackdrop, DialogPanel } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
export default function Modal(props: ModalProps){ export default function Modal({
const { display,
display, onClose,
onClose, className,
className, backgroundType = "blur",
backgroundType = "blur", top,
top, children
children }: Readonly<ModalProps>){
} = props;
return ( return (
<Dialog <Dialog
open={display} open={display}

View File

@@ -2,19 +2,18 @@ import type { ModalBodyProps } from "$/types/ModalTypes";
import clsx from "clsx"; import clsx from "clsx";
export default function ModalBody(props: ModalBodyProps){ export default function ModalBody({
const { className,
className, children,
children ...props
} = props; }: Readonly<ModalBodyProps>){
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-col items-center justify-start h-full w-full overflow-scroll", "flex flex-col items-center justify-start h-full w-full overflow-scroll",
className className
)} )}
{...props}
> >
{children} {children}
</div> </div>

View File

@@ -2,19 +2,18 @@ import type { ModalFooterProps } from "$/types/ModalTypes";
import clsx from "clsx"; import clsx from "clsx";
export default function ModalFooter(props: ModalFooterProps){ export default function ModalFooter({
const { className,
className, children,
children ...props
} = props; }: Readonly<ModalFooterProps>){
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-row items-center justify-center w-full rounded-b-lg", "flex flex-row items-center justify-center w-full rounded-b-lg",
className className
)} )}
{...props}
> >
<div <div
className="flex flex-row items-center justify-center mx-8 my-3" className="flex flex-row items-center justify-center mx-8 my-3"

View File

@@ -1,23 +1,23 @@
import Button from "$/component/button/Button";
import type { ModalHeaderProps } from "$/types/ModalTypes"; import type { ModalHeaderProps } from "$/types/ModalTypes";
import { DialogTitle } from "@headlessui/react"; import { DialogTitle } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
import { BsXLg } from "react-icons/bs"; import { BsXLg } from "react-icons/bs";
export default function ModalHeader(props: ModalHeaderProps){ export default function ModalHeader({
const { onClose,
onClose, className,
className, children,
children ...props
} = props; }: Readonly<ModalHeaderProps>){
return ( return (
<div <div
className={clsx( className={clsx(
"flex flex-row items-center justify-center w-full rounded-t-lg", "flex flex-row items-center justify-center w-full rounded-t-lg",
className className
)} )}
{...props}
> >
<DialogTitle <DialogTitle
as="div" as="div"
@@ -27,14 +27,14 @@ export default function ModalHeader(props: ModalHeaderProps){
</DialogTitle> </DialogTitle>
{ {
onClose && onClose &&
<div <Button
className="absolute top-1 right-1 cursor-pointer" className="absolute top-1 right-1 cursor-pointer"
onClick={onClose} onClick={onClose}
> >
<BsXLg <BsXLg
size={20} size={20}
/> />
</div> </Button>
} }
</div> </div>
); );

View File

@@ -8,7 +8,7 @@ export default function DarkModeSwitch(){
const updateTheme = () => { const updateTheme = () => {
if(theme === "system"){ if(theme === "system"){
if(window.document.documentElement.classList.contains("dark")){ if(globalThis.document.documentElement.classList.contains("dark")){
setTheme("light"); setTheme("light");
} }
else{ else{

View File

@@ -2,13 +2,11 @@ import type { NavBarProps } from "$/types/NavTypes";
import clsx from "clsx"; import clsx from "clsx";
export default function NavBar(props: NavBarProps){ export default function NavBar({
const { className,
className, children,
children ...props
} = props; }: Readonly<NavBarProps>){
return ( return (
<nav <nav
className={clsx( className={clsx(
@@ -16,6 +14,7 @@ export default function NavBar(props: NavBarProps){
"fixed top-0 left-0 w-full z-10", "fixed top-0 left-0 w-full z-10",
"flex flex-row flex-nowrap items-center justify-center" "flex flex-row flex-nowrap items-center justify-center"
)} )}
{...props}
> >
{children} {children}
</nav> </nav>

View File

@@ -3,14 +3,11 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
import { RiArrowRightSLine } from "react-icons/ri"; import { RiArrowRightSLine } from "react-icons/ri";
export default function PopoverMenu(props: PopoverMenuProps){ export default function PopoverMenu({
const { buttonContent,
buttonContent, anchor,
anchor, children
children }: Readonly<PopoverMenuProps>){
} = props;
return ( return (
<Popover> <Popover>
<PopoverButton <PopoverButton

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function DangerPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-danger-pill"
className={clsx(
className,
"bg-danger text-white"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function DarkPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-dark-pill"
className={clsx(
className,
"bg-dark text-white"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function InfoPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-info-pill"
className={clsx(
className,
"bg-info text-black"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function LightPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-light-pill"
className={clsx(
className,
"bg-light text-black"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function MoltenPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-molten-pill"
className={clsx(
className,
"bg-molten text-black"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,26 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
export default function Pill({
className,
rounding = "full",
...props
}: Readonly<PillProps>){
return (
<div
data-testid="mattrixwv-pill"
className={clsx(
className,
"px-2 text-sm whitespace-nowrap",
{
"rounded-sm": rounding === "sm",
"rounded": rounding === "md",
"rounded-lg": rounding === "lg",
"rounded-full": rounding === "full"
}
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function PrimaryPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-primary-pill"
className={clsx(
className,
"bg-primary text-white"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function SecondaryPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-secondary-pill"
className={clsx(
className,
"bg-secondary text-white"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function SuccessPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-success-pill"
className={clsx(
className,
"bg-success text-black"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function TertiaryPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-tertiary-pill"
className={clsx(
className,
"bg-tertiary text-white"
)}
{...props}
/>
);
}

View File

@@ -0,0 +1,20 @@
import type { PillProps } from "$/types/PillTypes";
import clsx from "clsx";
import Pill from "./Pill";
export default function WarningPill({
className,
...props
}: Readonly<PillProps>){
return (
<Pill
data-testid="mattrixwv-warning-pill"
className={clsx(
className,
"bg-warning text-black"
)}
{...props}
/>
);
}

View File

@@ -3,12 +3,9 @@ import { Tab } from "@headlessui/react";
import clsx from "clsx"; import clsx from "clsx";
export default function MattrixwvTab(props: MattrixwvTabProps){ export default function MattrixwvTab({
const { children
children }: Readonly<MattrixwvTabProps>){
} = props;
return ( return (
<Tab <Tab
className={clsx( className={clsx(

View File

@@ -7,13 +7,10 @@ import MattrixwvTabPanel from "./MattrixwvTabPanel";
import MattrixwvTabPanels from "./MattrixwvTabPanels"; import MattrixwvTabPanels from "./MattrixwvTabPanels";
export default function MattrixwvTabGroup(props: MattrixwvTabGroupProps){ export default function MattrixwvTabGroup({
const { tabs,
tabs, className
className }: Readonly<MattrixwvTabGroupProps>){
} = props;
return ( return (
<TabGroup <TabGroup
className={clsx( className={clsx(
@@ -24,9 +21,7 @@ export default function MattrixwvTabGroup(props: MattrixwvTabGroupProps){
<MattrixwvTabList> <MattrixwvTabList>
{ {
tabs.map((tab, index) => ( tabs.map((tab, index) => (
<MattrixwvTab <MattrixwvTab key={index}>
key={index}
>
{tab.tab} {tab.tab}
</MattrixwvTab> </MattrixwvTab>
)) ))
@@ -35,9 +30,7 @@ export default function MattrixwvTabGroup(props: MattrixwvTabGroupProps){
<MattrixwvTabPanels> <MattrixwvTabPanels>
{ {
tabs.map((tab, index) => ( tabs.map((tab, index) => (
<MattrixwvTabPanel <MattrixwvTabPanel key={index}>
key={index}
>
{tab.content} {tab.content}
</MattrixwvTabPanel> </MattrixwvTabPanel>
)) ))

View File

@@ -2,16 +2,11 @@ import type { MattrixwvTabListProps } from "$/types/TabTypes";
import { TabList } from "@headlessui/react"; import { TabList } from "@headlessui/react";
export default function MattrixwvTabList(props: MattrixwvTabListProps){ export default function MattrixwvTabList({
const { children
children }: Readonly<MattrixwvTabListProps>){
} = props;
return ( return (
<TabList <TabList className="flex flex-row items-center justify-start w-full border-b">
className="flex flex-row items-center justify-start w-full border-b"
>
{children} {children}
</TabList> </TabList>
); );

View File

@@ -2,16 +2,11 @@ import type { MattrixwvTabPanelProps } from "$/types/TabTypes";
import { TabPanel } from "@headlessui/react"; import { TabPanel } from "@headlessui/react";
export default function MattrixwvTabPanel(props: MattrixwvTabPanelProps){ export default function MattrixwvTabPanel({
const { children
children }: Readonly<MattrixwvTabPanelProps>){
} = props;
return ( return (
<TabPanel <TabPanel className="flex flex-row items-start justify-center w-full h-full">
className="flex flex-row items-start justify-center w-full h-full"
>
{children} {children}
</TabPanel> </TabPanel>
); );

View File

@@ -2,16 +2,11 @@ import type { MattrixwvTabPanelsProps } from "$/types/TabTypes";
import { TabPanels } from "@headlessui/react"; import { TabPanels } from "@headlessui/react";
export default function MattrixwvTabPanels(props: MattrixwvTabPanelsProps){ export default function MattrixwvTabPanels({
const { children
children }: Readonly<MattrixwvTabPanelsProps>){
} = props;
return ( return (
<TabPanels <TabPanels className="flex flex-row items-start justify-center w-full h-full overflow-scroll">
className="flex flex-row items-start justify-center w-full h-full overflow-scroll"
>
{children} {children}
</TabPanels> </TabPanels>
); );

View File

@@ -6,7 +6,7 @@ import clsx from "clsx";
export default function Toaster({ export default function Toaster({
toast, toast,
className className
}: ToasterProps){ }: Readonly<ToasterProps>){
return ( return (
<Transition <Transition
show={toast.length > 1 || (toast.length === 1 && toast[0].hideTime > new Date())} show={toast.length > 1 || (toast.length === 1 && toast[0].hideTime > new Date())}

View File

@@ -10,7 +10,7 @@ const themeInitialState: ThemeProviderState = {
const ThemeProviderContext = createContext<ThemeProviderState>(themeInitialState); const ThemeProviderContext = createContext<ThemeProviderState>(themeInitialState);
export default function ThemeProvider(props: ThemeProviderProps){ export default function ThemeProvider(props: Readonly<ThemeProviderProps>){
const { const {
children, children,
defaultTheme = "system", defaultTheme = "system",
@@ -20,12 +20,12 @@ export default function ThemeProvider(props: ThemeProviderProps){
const [ theme, setTheme ] = useState<Theme>((localStorage.getItem(storageKey) as Theme) || defaultTheme); const [ theme, setTheme ] = useState<Theme>((localStorage.getItem(storageKey) as Theme) || defaultTheme);
useEffect(() => { useEffect(() => {
const root = window.document.documentElement; const root = globalThis.document.documentElement;
root.classList.remove("light", "dark"); root.classList.remove("light", "dark");
if(theme === "system"){ if(theme === "system"){
const systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; const systemTheme = globalThis.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
root.classList.add(systemTheme); root.classList.add(systemTheme);
} }

View File

@@ -19,7 +19,7 @@ const ToasterProviderContext = createContext<ToastProviderState>(toastInitialSta
export default function ToasterProvider({ export default function ToasterProvider({
className, className,
children children
}: ToastProviderProps){ }: Readonly<ToastProviderProps>){
const [ toast, setToast ] = useState<Toast[]>([]); const [ toast, setToast ] = useState<Toast[]>([]);

View File

@@ -1,4 +1,4 @@
import type { ButtonHTMLAttributes } from "react"; import type { ComponentProps } from "react";
export type ButtonRounding = "none" | "sm" | "md" | "lg" | "full"; export type ButtonRounding = "none" | "sm" | "md" | "lg" | "full";
@@ -7,7 +7,7 @@ export type ButtonSize = "xs" | "sm" | "md" | "lg" | "xl";
export type ButtonVariant = "standard" | "outline" | "ghost" | "outline-ghost" | "icon"; export type ButtonVariant = "standard" | "outline" | "ghost" | "outline-ghost" | "icon";
export interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "size" | "type"> { export interface ButtonProps extends Omit<ComponentProps<"button">, "size" | "type"> {
type?: "button" | "submit" | "reset"; type?: "button" | "submit" | "reset";
rounding?: ButtonRounding; rounding?: ButtonRounding;
shape?: ButtonShape; shape?: ButtonShape;

View File

@@ -1,5 +1,5 @@
import type React from "react"; import type React from "react";
import type { ChangeEventHandler, InputHTMLAttributes } from "react"; import type { ChangeEventHandler, ComponentProps } from "react";
export interface TextInputProps { export interface TextInputProps {
@@ -11,7 +11,6 @@ export interface TextInputProps {
maxLength?: number; maxLength?: number;
spellCheck?: boolean; spellCheck?: boolean;
placeholder?: string; placeholder?: string;
defaultValue?: string;
value?: string; value?: string;
onChange?: ChangeEventHandler<HTMLInputElement>; onChange?: ChangeEventHandler<HTMLInputElement>;
disabled?: boolean; disabled?: boolean;
@@ -26,7 +25,6 @@ export interface TextAreaProps {
maxLength?: number; maxLength?: number;
spellCheck?: boolean; spellCheck?: boolean;
placeholder?: string; placeholder?: string;
defaultValue?: string;
value?: string; value?: string;
disabled?: boolean; disabled?: boolean;
rows?: number; rows?: number;
@@ -35,9 +33,10 @@ export interface TextAreaProps {
} }
export interface SelectInputProps { export interface SelectInputProps {
label: React.ReactNode; placeholder: React.ReactNode;
value?: string; value?: string;
onChange?: (newValue: string) => void; onChange?: (newValue: string) => void;
disabled?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
@@ -45,6 +44,7 @@ export interface OptionInputProps {
id?: string; id?: string;
className?: string; className?: string;
value: string; value: string;
disabled?: boolean;
children: React.ReactNode; children: React.ReactNode;
} }
@@ -88,9 +88,11 @@ export interface NumberInputProps {
name?: string; name?: string;
min?: number; min?: number;
max?: number; max?: number;
defaultValue?: number; step?: number;
value?: number; prefix?: string;
onChange?: (newValue: number) => void; suffix?: string;
value: number;
onChange: (newValue: number) => void;
disabled?: boolean; disabled?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
} }
@@ -102,20 +104,21 @@ export interface NumberSliderProps {
min?: number; min?: number;
max?: number; max?: number;
step?: number; step?: number;
defaultValue?: number; value: number;
value?: number; onChange: (newValue: number) => void;
onChange?: (newValue: number) => void;
disabled?: boolean; disabled?: boolean;
ariaLabel?: string;
} }
export interface FileInputProps { export interface FileInputProps {
id?: string; id?: string;
className?: string; className?: string;
name?: string; name?: string;
ariaLabel?: string;
minSize?: number; minSize?: number;
maxSize?: number; maxSize?: number;
showFileName?: boolean; showFileName: boolean;
showSize?: boolean; showSize: boolean;
onChange?: (newFile: File | undefined) => void; onChange?: (newFile: File | undefined) => void;
disabled?: boolean; disabled?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
@@ -125,7 +128,6 @@ export interface FileInputProps {
export type CheckboxSize = "none" | "xs" | "sm" | "md" | "lg" | "xl"; export type CheckboxSize = "none" | "xs" | "sm" | "md" | "lg" | "xl";
export interface CheckboxProps { export interface CheckboxProps {
id?: string;
className?: string; className?: string;
labelClassName?: string; labelClassName?: string;
name?: string; name?: string;
@@ -164,7 +166,7 @@ export interface RadioListProps {
} }
export interface DateInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "step">{ export interface DateInputProps extends Omit<ComponentProps<"input">, "value" | "onChange" | "step">{
step?: number; step?: number;
value: Date | undefined; value: Date | undefined;
onChange: (newValue: Date | undefined) => void; onChange: (newValue: Date | undefined) => void;

View File

@@ -1,11 +1,15 @@
/* eslint-disable @typescript-eslint/no-empty-object-type */ /* eslint-disable @typescript-eslint/no-empty-object-type */
interface LoadingDefaultProps { interface LoadingDefaultProps {
width?: string | number; size?: number;
height?: string | number; width?: number;
height?: number;
className?: string; className?: string;
animationDuration?: number; animationDuration?: number;
color?: string;
stroke?: string; stroke?: string;
fill?: string; fill?: string;
ariaLabel?: string;
} }
@@ -26,13 +30,16 @@ export interface LoadingDotsProps extends LoadingDefaultProps {
} }
export interface CirclePulsingDotsProps { export interface CirclePulsingDotsProps {
width?: string | number; size?: number;
height?: string | number; width?: number;
height?: number;
className?: string; className?: string;
rotationAnimationDuration?: number; rotationAnimationDuration?: number;
growingAnimationDuration?: number; growingAnimationDuration?: number;
color?: string;
stroke?: string; stroke?: string;
fill?: string; fill?: string;
ariaLabel?: string;
} }
export interface CircleSpinningDotProps extends LoadingDefaultProps { export interface CircleSpinningDotProps extends LoadingDefaultProps {

View File

@@ -1,7 +1,7 @@
import type { HTMLProps } from "react"; import type { ComponentProps } from "react";
export interface MessageBlockProps extends HTMLProps<HTMLDivElement> { export interface MessageBlockProps extends ComponentProps<"div"> {
outline?: "none" | "sm" | "md" | "lg"; outline?: "none" | "sm" | "md" | "lg";
rounded?: "none" | "xs" | "sm" | "md" | "lg" | "xl"; rounded?: "none" | "xs" | "sm" | "md" | "lg" | "xl";
} }

View File

@@ -1,18 +1,21 @@
import type { ComponentProps } from "react";
export type ModalBackgroundType = "darken" | "lighten" | "blur" | "darken-blur" | "lighten-blur" | "darken-blur-radial" | "lighten-blur-radial" | "transparent" | "none"; export type ModalBackgroundType = "darken" | "lighten" | "blur" | "darken-blur" | "lighten-blur" | "darken-blur-radial" | "lighten-blur-radial" | "transparent" | "none";
export interface ModalHeaderProps { export interface ModalHeaderProps extends ComponentProps<"div"> {
onClose?: () => void; onClose?: () => void;
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export interface ModalFooterProps { export interface ModalFooterProps extends ComponentProps<"div"> {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }
export interface ModalBodyProps { export interface ModalBodyProps extends ComponentProps<"div"> {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }

View File

@@ -1,3 +1,6 @@
import type { ComponentProps } from "react";
export type Align = "start" | "end"; export type Align = "start" | "end";
export type Placement = "top" | "bottom" | "left" | "right"; export type Placement = "top" | "bottom" | "left" | "right";
export type AnchorTo = `${Placement}` | `${Placement} ${Align}`; export type AnchorTo = `${Placement}` | `${Placement} ${Align}`;
@@ -9,7 +12,7 @@ export interface PopoverMenuProps {
children: React.ReactNode; children: React.ReactNode;
} }
export interface NavBarProps { export interface NavBarProps extends ComponentProps<"nav"> {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
} }

9
lib/types/PillTypes.ts Normal file
View File

@@ -0,0 +1,9 @@
import type { ComponentProps } from "react";
export type PillRounding = "none" | "sm" | "md" | "lg" | "full";
export interface PillProps extends ComponentProps<"div"> {
rounding?: PillRounding;
}

View File

@@ -0,0 +1,14 @@
import { useEffect, useState } from "react";
export function usePrefersReducedMotion(){
const [ reduced, setReduced ] = useState(false);
useEffect(() => {
const media = globalThis.matchMedia("(prefers-reduced-motion: reduce)");
// eslint-disable-next-line react-hooks/set-state-in-effect
setReduced(media.matches);
}, [ ]);
return reduced;
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "mattrixwv-components", "name": "mattrixwv-components",
"version": "0.0.8", "version": "0.0.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mattrixwv-components", "name": "mattrixwv-components",
"version": "0.0.8", "version": "0.0.9",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",

View File

@@ -1,7 +1,7 @@
{ {
"name": "mattrixwv-components", "name": "mattrixwv-components",
"private": false, "private": false,
"version": "0.0.8", "version": "0.0.9",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -18,12 +18,14 @@
"versionMinor": "npm version minor", "versionMinor": "npm version minor",
"versionMajor": "npm version major" "versionMajor": "npm version major"
}, },
"dependencies": {
"clsx": "^2.1.1",
"react-icons": "^5.5.0"
},
"peerDependencies": { "peerDependencies": {
"@headlessui/react": "^2.2.9", "@headlessui/react": "^2.2.9",
"clsx": "^2.1.1",
"react": "^19.2.3", "react": "^19.2.3",
"react-dom": "^19.2.3", "react-dom": "^19.2.3"
"react-icons": "^5.5.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",

View File

@@ -18,7 +18,7 @@ declare module "@tanstack/react-router" {
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<ThemeProvider defaultTheme="dark"> <ThemeProvider defaultTheme="dark">
<ToasterProvider className="bg-zinc-700 text-white px-4 py-2 min-w-32 max-w-128 rounded-lg gap-y-4"> <ToasterProvider className="bg-zinc-700 text-white px-4 py-2 min-w-32 max-w-lg rounded-lg gap-y-4">
<RouterProvider router={router}/> <RouterProvider router={router}/>
</ToasterProvider> </ToasterProvider>
</ThemeProvider> </ThemeProvider>

View File

@@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as TabIndexRouteImport } from './routes/tab/index' import { Route as TabIndexRouteImport } from './routes/tab/index'
import { Route as ProgressIndexRouteImport } from './routes/progress/index' import { Route as ProgressIndexRouteImport } from './routes/progress/index'
import { Route as PillIndexRouteImport } from './routes/pill/index'
import { Route as ModalIndexRouteImport } from './routes/modal/index' import { Route as ModalIndexRouteImport } from './routes/modal/index'
import { Route as MessageIndexRouteImport } from './routes/message/index' import { Route as MessageIndexRouteImport } from './routes/message/index'
import { Route as LoadingIndexRouteImport } from './routes/loading/index' import { Route as LoadingIndexRouteImport } from './routes/loading/index'
@@ -33,6 +34,11 @@ const ProgressIndexRoute = ProgressIndexRouteImport.update({
path: '/progress/', path: '/progress/',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const PillIndexRoute = PillIndexRouteImport.update({
id: '/pill/',
path: '/pill/',
getParentRoute: () => rootRouteImport,
} as any)
const ModalIndexRoute = ModalIndexRouteImport.update({ const ModalIndexRoute = ModalIndexRouteImport.update({
id: '/modal/', id: '/modal/',
path: '/modal/', path: '/modal/',
@@ -66,6 +72,7 @@ export interface FileRoutesByFullPath {
'/loading': typeof LoadingIndexRoute '/loading': typeof LoadingIndexRoute
'/message': typeof MessageIndexRoute '/message': typeof MessageIndexRoute
'/modal': typeof ModalIndexRoute '/modal': typeof ModalIndexRoute
'/pill': typeof PillIndexRoute
'/progress': typeof ProgressIndexRoute '/progress': typeof ProgressIndexRoute
'/tab': typeof TabIndexRoute '/tab': typeof TabIndexRoute
} }
@@ -76,6 +83,7 @@ export interface FileRoutesByTo {
'/loading': typeof LoadingIndexRoute '/loading': typeof LoadingIndexRoute
'/message': typeof MessageIndexRoute '/message': typeof MessageIndexRoute
'/modal': typeof ModalIndexRoute '/modal': typeof ModalIndexRoute
'/pill': typeof PillIndexRoute
'/progress': typeof ProgressIndexRoute '/progress': typeof ProgressIndexRoute
'/tab': typeof TabIndexRoute '/tab': typeof TabIndexRoute
} }
@@ -87,6 +95,7 @@ export interface FileRoutesById {
'/loading/': typeof LoadingIndexRoute '/loading/': typeof LoadingIndexRoute
'/message/': typeof MessageIndexRoute '/message/': typeof MessageIndexRoute
'/modal/': typeof ModalIndexRoute '/modal/': typeof ModalIndexRoute
'/pill/': typeof PillIndexRoute
'/progress/': typeof ProgressIndexRoute '/progress/': typeof ProgressIndexRoute
'/tab/': typeof TabIndexRoute '/tab/': typeof TabIndexRoute
} }
@@ -99,6 +108,7 @@ export interface FileRouteTypes {
| '/loading' | '/loading'
| '/message' | '/message'
| '/modal' | '/modal'
| '/pill'
| '/progress' | '/progress'
| '/tab' | '/tab'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
@@ -109,6 +119,7 @@ export interface FileRouteTypes {
| '/loading' | '/loading'
| '/message' | '/message'
| '/modal' | '/modal'
| '/pill'
| '/progress' | '/progress'
| '/tab' | '/tab'
id: id:
@@ -119,6 +130,7 @@ export interface FileRouteTypes {
| '/loading/' | '/loading/'
| '/message/' | '/message/'
| '/modal/' | '/modal/'
| '/pill/'
| '/progress/' | '/progress/'
| '/tab/' | '/tab/'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
@@ -130,6 +142,7 @@ export interface RootRouteChildren {
LoadingIndexRoute: typeof LoadingIndexRoute LoadingIndexRoute: typeof LoadingIndexRoute
MessageIndexRoute: typeof MessageIndexRoute MessageIndexRoute: typeof MessageIndexRoute
ModalIndexRoute: typeof ModalIndexRoute ModalIndexRoute: typeof ModalIndexRoute
PillIndexRoute: typeof PillIndexRoute
ProgressIndexRoute: typeof ProgressIndexRoute ProgressIndexRoute: typeof ProgressIndexRoute
TabIndexRoute: typeof TabIndexRoute TabIndexRoute: typeof TabIndexRoute
} }
@@ -157,6 +170,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProgressIndexRouteImport preLoaderRoute: typeof ProgressIndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/pill/': {
id: '/pill/'
path: '/pill'
fullPath: '/pill'
preLoaderRoute: typeof PillIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/modal/': { '/modal/': {
id: '/modal/' id: '/modal/'
path: '/modal' path: '/modal'
@@ -202,6 +222,7 @@ const rootRouteChildren: RootRouteChildren = {
LoadingIndexRoute: LoadingIndexRoute, LoadingIndexRoute: LoadingIndexRoute,
MessageIndexRoute: MessageIndexRoute, MessageIndexRoute: MessageIndexRoute,
ModalIndexRoute: ModalIndexRoute, ModalIndexRoute: ModalIndexRoute,
PillIndexRoute: PillIndexRoute,
ProgressIndexRoute: ProgressIndexRoute, ProgressIndexRoute: ProgressIndexRoute,
TabIndexRoute: TabIndexRoute, TabIndexRoute: TabIndexRoute,
} }

View File

@@ -11,6 +11,7 @@ const navLinks = [
{ to: "/loading", label: "Loading" }, { to: "/loading", label: "Loading" },
{ to: "/message", label: "Message" }, { to: "/message", label: "Message" },
{ to: "/modal", label: "Modal" }, { to: "/modal", label: "Modal" },
{ to: "/pill", label: "Pill" },
{ to: "/progress", label: "Progress" }, { to: "/progress", label: "Progress" },
{ to: "/tab", label: "Tab" } { to: "/tab", label: "Tab" }
]; ];
@@ -20,7 +21,7 @@ export const Route = createRootRoute({
component: () => ( component: () => (
<> <>
<NavBar <NavBar
className="bg-neutral-300 dark:bg-neutral-900 h-12" className="bg-neutral-300 dark:bg-neutral-900 h-12 gap-x-4"
> >
<div <div
className="flex flex-row items-center justify-end gap-x-4" className="flex flex-row items-center justify-end gap-x-4"

View File

@@ -120,10 +120,10 @@ function generateTabContent(Fn: (props: ButtonProps) => JSX.Element, onClick: ()
function ButtonDisplay({ function ButtonDisplay({
title, title,
children children
}:{ }: Readonly<{
title: React.ReactNode; title: React.ReactNode;
children: React.ReactNode; children: React.ReactNode;
}){ }>){
return ( return (
<div <div
className="flex flex-col items-center justify-center gap-y-2" className="flex flex-col items-center justify-center gap-y-2"

View File

@@ -1,13 +1,13 @@
import { DangerMessageBlock, DarkMessageBlock, LightMessageBlock, PrimaryMessageBlock, SecondaryMessageBlock, SuccessMessageBlock, TertiaryMessageBlock, WarningMessageBlock } from '$/component/message'; import { DangerMessageBlock, DarkMessageBlock, LightMessageBlock, PrimaryMessageBlock, SecondaryMessageBlock, SuccessMessageBlock, TertiaryMessageBlock, WarningMessageBlock } from '$/component/message';
import InfoMessageBlock from '$/component/message/InfoMessageBlock'; import InfoMessageBlock from "$/component/message/InfoMessageBlock";
import MoltenMessageBlock from '$/component/message/MoltenMessageBlock'; import MoltenMessageBlock from "$/component/message/MoltenMessageBlock";
import { MattrixwvTabGroup } from '$/component/tab'; import { MattrixwvTabGroup } from "$/component/tab";
import type { MessageBlockProps } from '$/types/MessageTypes'; import type { MessageBlockProps } from "$/types/MessageTypes";
import type { TabGroupContent } from '$/types/TabTypes'; import type { TabGroupContent } from "$/types/TabTypes";
import { createFileRoute } from '@tanstack/react-router'; import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute('/message/')({ export const Route = createFileRoute("/message/")({
component: MessagePage component: MessagePage
}); });

View File

@@ -77,10 +77,10 @@ function ModalPage(){
function DemoCenteredModal({ function DemoCenteredModal({
display, display,
onClose onClose
}:{ }: Readonly<{
display: boolean; display: boolean;
onClose: () => void; onClose: () => void;
}){ }>){
return ( return (
<Modal <Modal
display={display} display={display}
@@ -97,10 +97,10 @@ function DemoCenteredModal({
function DemoTopModal({ function DemoTopModal({
display, display,
onClose onClose
}:{ }: Readonly<{
display: boolean; display: boolean;
onClose: () => void; onClose: () => void;
}){ }>){
return ( return (
<Modal <Modal
display={display} display={display}

56
src/routes/pill/index.tsx Normal file
View File

@@ -0,0 +1,56 @@
import DangerPill from "$/component/pill/DangerPill";
import DarkPill from "$/component/pill/DarkPill";
import InfoPill from "$/component/pill/InfoPill";
import LightPill from "$/component/pill/LightPill";
import MoltenPill from "$/component/pill/MoltenPill";
import PrimaryPill from "$/component/pill/PrimaryPill";
import SecondaryPill from "$/component/pill/SecondaryPill";
import SuccessPill from "$/component/pill/SuccessPill";
import TertiaryPill from "$/component/pill/TertiaryPill";
import WarningPill from "$/component/pill/WarningPill";
import type { PillProps } from "$/types/PillTypes";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/pill/")({
component: PillPage
});
function PillPage(){
return (
<div className="flex flex-col items-center justify-center gap-y-8">
<PillLayout PillType={PrimaryPill} pillName="Primary"/>
<PillLayout PillType={SecondaryPill} pillName="Secondary"/>
<PillLayout PillType={TertiaryPill} pillName="Tertiary"/>
<PillLayout PillType={InfoPill} pillName="Info"/>
<PillLayout PillType={SuccessPill} pillName="Success"/>
<PillLayout PillType={WarningPill} pillName="Warning"/>
<PillLayout PillType={DangerPill} pillName="Danger"/>
<PillLayout PillType={MoltenPill} pillName="Molten"/>
<PillLayout PillType={DarkPill} pillName="Dark"/>
<PillLayout PillType={LightPill} pillName="Light"/>
</div>
);
}
function PillLayout({
PillType,
pillName
}: Readonly<{
PillType: (props: PillProps) => React.ReactNode;
pillName: string;
}>){
return (
<div className="flex flex-col items-center justify-center gap-y-2">
<h2 className="text-2xl">{pillName}</h2>
<div className="flex flex-row items-center justify-center gap-x-8">
<PillType rounding="none">None</PillType>
<PillType rounding="sm">Small</PillType>
<PillType rounding="md">Medium</PillType>
<PillType rounding="lg">Large</PillType>
<PillType rounding="full">Full</PillType>
</div>
</div>
);
}

View File

@@ -128,13 +128,13 @@ function ProgressPage() {
function ProgressBlock({ function ProgressBlock({
label, label,
children children
}:{ }: Readonly<{
label: string; label: string;
children: React.ReactNode; children: React.ReactNode;
}){ }>){
return ( return (
<div <div
className="flex flex-col items-center justify-center w-128 gap-y-2" className="flex flex-col items-center justify-center w-lg gap-y-2"
> >
<h3 <h3
className="font-bold text-2xl" className="font-bold text-2xl"

View File

@@ -14,6 +14,7 @@ import WarningCheckbox from "$/component/input/checkbox/WarningCheckbox";
import DateInput from "$/component/input/date/DateInput"; import DateInput from "$/component/input/date/DateInput";
import DateTimeInput from "$/component/input/date/DateTimeInput"; import DateTimeInput from "$/component/input/date/DateTimeInput";
import TimeInput from "$/component/input/date/TimeInput"; import TimeInput from "$/component/input/date/TimeInput";
import NumberSlider from "$/component/input/number/NumberSlider";
import DangerRadioButton from "$/component/input/radio/DangerRadioButton"; import DangerRadioButton from "$/component/input/radio/DangerRadioButton";
import DarkRadioButton from "$/component/input/radio/DarkRadioButton"; import DarkRadioButton from "$/component/input/radio/DarkRadioButton";
import InfoRadioButton from "$/component/input/radio/InfoRadioButton"; import InfoRadioButton from "$/component/input/radio/InfoRadioButton";
@@ -572,19 +573,20 @@ export function TextContent(){
]; ];
const [ selected, setSelected ] = useState(selectOptions[0]); const [ selected, setSelected ] = useState(selectOptions[0]);
const [ numberValue, setNumberValue ] = useState(0);
return ( return (
<div <div
className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full" className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full"
> >
<TextDisplay title="Text Input"> <GeneralInputDisplay title="Text Input">
<TextInput placeholder="Text Input" labelClassName="bg-(--bg-color) peer-focus:bg-(--bg-color)"/> <TextInput placeholder="Text Input" labelClassName="bg-(--bg-color) peer-focus:bg-(--bg-color)"/>
</TextDisplay> </GeneralInputDisplay>
<TextDisplay title="Text Area"> <GeneralInputDisplay title="Text Area">
<TextArea placeholder="Textarea" className="resize" labelClassName="bg-(--bg-color) peer-focus:bg-(--bg-color)"/> <TextArea placeholder="Textarea" className="resize" labelClassName="bg-(--bg-color) peer-focus:bg-(--bg-color)"/>
</TextDisplay> </GeneralInputDisplay>
<TextDisplay title="Select"> <GeneralInputDisplay title="Select">
<SelectInput label={selected.label} onChange={(newValue) => setSelected(selectOptions.find((option) => option.value === newValue) || selectOptions[0])}> <SelectInput placeholder={selected.label} onChange={(newValue) => setSelected(selectOptions.find((option) => option.value === newValue) || selectOptions[0])}>
{ {
selectOptions.map((option) => ( selectOptions.map((option) => (
<OptionInput <OptionInput
@@ -597,31 +599,35 @@ export function TextContent(){
)) ))
} }
</SelectInput> </SelectInput>
</TextDisplay> </GeneralInputDisplay>
<TextDisplay <GeneralInputDisplay
title="Number Input" title="Number Input"
> >
<NumberInput <NumberInput
labelClassName="bg-(--bg-color)" labelClassName="bg-(--bg-color)"
value={numberValue}
onChange={setNumberValue}
> >
Number Test Number Test
</NumberInput> </NumberInput>
</TextDisplay> </GeneralInputDisplay>
{/* {/* */
<TextDisplay <GeneralInputDisplay
title="Number Slider" title="Number Slider"
> >
<NumberSlider <NumberSlider
min={0} min={0}
max={10} max={10}
value={numberValue}
onChange={setNumberValue}
/> />
</TextDisplay> </GeneralInputDisplay>
*/} /* */}
</div> </div>
); );
} }
function TextDisplay({ function GeneralInputDisplay({
title, title,
children children
}:Readonly<{ }:Readonly<{
@@ -643,13 +649,13 @@ export function FileContent(){
<div <div
className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full" className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full"
> >
<FileDisplay title="File Input"> <GeneralInputDisplay title="File Input">
<FileInput <FileInput
showFileName={true} showFileName={true}
showSize={true} showSize={true}
/> />
</FileDisplay> </GeneralInputDisplay>
<FileDisplay title="Drag and Drop File Input"> <GeneralInputDisplay title="Drag and Drop File Input">
<DragAndDropFileInput <DragAndDropFileInput
showFileName={true} showFileName={true}
showSize={true} showSize={true}
@@ -658,24 +664,7 @@ export function FileContent(){
> >
Drag And Drop File Input Drag And Drop File Input
</DragAndDropFileInput> </DragAndDropFileInput>
</FileDisplay> </GeneralInputDisplay>
</div>
);
}
function FileDisplay({
title,
children
}:Readonly<{
title: React.ReactNode;
children: React.ReactNode;
}>){
return (
<div
className="flex flex-col items-center justify-center gap-y-2 w-full"
>
<h2 className="text-2xl">{title}</h2>
{children}
</div> </div>
); );
} }
@@ -685,7 +674,7 @@ export function CheckboxContent(){
<div <div
className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full" className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full"
> >
<CheckboxDisplay title="Checkbox"> <GeneralInputDisplay title="Checkbox">
<div className="flex flex-row items-center justify-center gap-x-8"> <div className="flex flex-row items-center justify-center gap-x-8">
<MattrixwvCheckbox className="group-data-checked:stroke-white group-data-checked:bg-amber-400" defaultChecked={true}>Default</MattrixwvCheckbox> <MattrixwvCheckbox className="group-data-checked:stroke-white group-data-checked:bg-amber-400" defaultChecked={true}>Default</MattrixwvCheckbox>
<PrimaryCheckbox defaultChecked={true} >Primary</PrimaryCheckbox> <PrimaryCheckbox defaultChecked={true} >Primary</PrimaryCheckbox>
@@ -699,8 +688,8 @@ export function CheckboxContent(){
<LightCheckbox defaultChecked={true}>Light</LightCheckbox> <LightCheckbox defaultChecked={true}>Light</LightCheckbox>
<DarkCheckbox defaultChecked={true}>Dark</DarkCheckbox> <DarkCheckbox defaultChecked={true}>Dark</DarkCheckbox>
</div> </div>
</CheckboxDisplay> </GeneralInputDisplay>
<CheckboxDisplay title="Checks"> <GeneralInputDisplay title="Checks">
<div className="flex flex-row items-center justify-center gap-x-8"> <div className="flex flex-row items-center justify-center gap-x-8">
<MattrixwvCheckbox className="group-data-checked:stroke-amber-400" defaultChecked={true} showBox={false}>Default</MattrixwvCheckbox> <MattrixwvCheckbox className="group-data-checked:stroke-amber-400" defaultChecked={true} showBox={false}>Default</MattrixwvCheckbox>
<PrimaryCheckbox defaultChecked={true} showBox={false}>Primary</PrimaryCheckbox> <PrimaryCheckbox defaultChecked={true} showBox={false}>Primary</PrimaryCheckbox>
@@ -714,24 +703,7 @@ export function CheckboxContent(){
<LightCheckbox defaultChecked={true} showBox={false}>Light</LightCheckbox> <LightCheckbox defaultChecked={true} showBox={false}>Light</LightCheckbox>
<DarkCheckbox defaultChecked={true} showBox={false}>Dark</DarkCheckbox> <DarkCheckbox defaultChecked={true} showBox={false}>Dark</DarkCheckbox>
</div> </div>
</CheckboxDisplay> </GeneralInputDisplay>
</div>
);
}
function CheckboxDisplay({
title,
children
}:Readonly<{
title: React.ReactNode;
children: React.ReactNode;
}>){
return (
<div
className="flex flex-col items-center justify-center gap-y-2 w-full"
>
<h2 className="text-2xl">{title}</h2>
{children}
</div> </div>
); );
} }
@@ -741,7 +713,7 @@ export function RadioContent(){
<div <div
className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full" className="flex flex-col items-center justify-center gap-y-8 mt-8 w-full"
> >
<RadioDisplay title="Sizes"> <GeneralInputDisplay title="Sizes">
<RadioList <RadioList
> >
<PrimaryRadioButton size="xs" value="xs">Extra Small</PrimaryRadioButton> <PrimaryRadioButton size="xs" value="xs">Extra Small</PrimaryRadioButton>
@@ -750,8 +722,8 @@ export function RadioContent(){
<PrimaryRadioButton size="lg" value="lg">Large</PrimaryRadioButton> <PrimaryRadioButton size="lg" value="lg">Large</PrimaryRadioButton>
<PrimaryRadioButton size="xl" value="xl">Extra Large</PrimaryRadioButton> <PrimaryRadioButton size="xl" value="xl">Extra Large</PrimaryRadioButton>
</RadioList> </RadioList>
</RadioDisplay> </GeneralInputDisplay>
<RadioDisplay title="Radio Checked"> <GeneralInputDisplay title="Radio Checked">
<div <div
className="flex flex-row items-center justify-center gap-x-8" className="flex flex-row items-center justify-center gap-x-8"
> >
@@ -766,8 +738,8 @@ export function RadioContent(){
<RadioList value="Light"><LightRadioButton value="Light">Light</LightRadioButton></RadioList> <RadioList value="Light"><LightRadioButton value="Light">Light</LightRadioButton></RadioList>
<RadioList value="Dark"><DarkRadioButton value="Dark">Dark</DarkRadioButton></RadioList> <RadioList value="Dark"><DarkRadioButton value="Dark">Dark</DarkRadioButton></RadioList>
</div> </div>
</RadioDisplay> </GeneralInputDisplay>
<RadioDisplay title="Radio Horizontal"> <GeneralInputDisplay title="Radio Horizontal">
<RadioList <RadioList
direction="horizontal" direction="horizontal"
> >
@@ -783,8 +755,8 @@ export function RadioContent(){
<LightRadioButton value="light">Light</LightRadioButton> <LightRadioButton value="light">Light</LightRadioButton>
<DarkRadioButton value="dark">Dark</DarkRadioButton> <DarkRadioButton value="dark">Dark</DarkRadioButton>
</RadioList> </RadioList>
</RadioDisplay> </GeneralInputDisplay>
<RadioDisplay title="Radio Vertical"> <GeneralInputDisplay title="Radio Vertical">
<RadioList <RadioList
direction="vertical" direction="vertical"
> >
@@ -800,24 +772,7 @@ export function RadioContent(){
<LightRadioButton value="light">Light</LightRadioButton> <LightRadioButton value="light">Light</LightRadioButton>
<DarkRadioButton value="dark">Dark</DarkRadioButton> <DarkRadioButton value="dark">Dark</DarkRadioButton>
</RadioList> </RadioList>
</RadioDisplay> </GeneralInputDisplay>
</div>
);
}
function RadioDisplay({
title,
children
}:Readonly<{
title: React.ReactNode;
children: React.ReactNode;
}>){
return (
<div
className="flex flex-col items-center justify-center gap-y-2 w-full"
>
<h2 className="text-2xl">{title}</h2>
{children}
</div> </div>
); );
} }

View File

@@ -47,10 +47,10 @@ import type { JSX } from "react";
function LoadingGroup({ function LoadingGroup({
label, label,
elements elements
}:{ }: Readonly<{
label: React.ReactNode; label: React.ReactNode;
elements: JSX.Element[]; elements: JSX.Element[];
}){ }>){
return ( return (
<div <div
className="flex flex-col items-center justify-center" className="flex flex-col items-center justify-center"

Some files were not shown because too many files have changed in this diff Show More