Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.10.8 #145

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fictoan-react",
"version": "1.10.7",
"version": "1.10.8",
"private": false,
"description": "A full-featured, designer-friendly, yet performant framework with plain-English props and focus on rapid iteration.",
"repository": {
Expand Down
9 changes: 8 additions & 1 deletion src/components/Element/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ElementType, HTMLProps } from "react";
import { ElementType, FormEvent, HTMLProps } from "react";

export const DefaultColours = [
"red",
Expand Down Expand Up @@ -137,9 +137,16 @@ export interface CommonProps {
export interface CommonAndHTMLProps<T extends {}>
extends CommonProps, Omit<HTMLProps<T>, "as" | "size" | "ref" | "shape"> {}

// Fictoan has two different types of event handlers, one for standard events and one for direct values
// This generic event handler type is a union of the two
export type FlexibleEventHandler<T, V = any> =
| ((event: T) => void)
| ((value: V) => void);

// prettier-ignore
export interface ElementProps<T extends {}> extends CommonProps, Omit<HTMLProps<T>, "as" | "ref" | "shape"> {
as ? : ElementType;
className ? : string;
ariaLabel ? : string;
onChange ? : FlexibleEventHandler<FormEvent<T>, any>;
}
91 changes: 50 additions & 41 deletions src/components/Form/BaseInputComponent/BaseInputComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// FRAMEWORK ===========================================================================================================
import React from "react";
import React, { FormEvent } from "react";

// FICTOAN =============================================================================================================
import { Element } from "../../Element/Element";
Expand All @@ -24,50 +24,59 @@ export const BaseInputComponent = React.forwardRef(
validateThis,
classNames,
children,
onChange,
...inputProps
}: BaseInputComponentWithIconProps<K>,
ref: React.LegacyRef<InputElementType>
) => (
<FormItem
data-form-item
required={inputProps.required}
>
{/* LABEL ////////////////////////////////////////////////////////////////////////////////////////////// */}
{label && <InputLabel label={label} htmlFor={inputProps.id} />}
) => {
return (
<FormItem
data-form-item
required={inputProps.required}
>
{/* LABEL ////////////////////////////////////////////////////////////////////////////////////////////// */}
{label && <InputLabel label={label} htmlFor={inputProps.id} />}

{/* MAIN INPUT ///////////////////////////////////////////////////////////////////////////////////////// */}
<Div data-input-wrapper>
{/* MAIN INPUT */}
<Element<K>
as={Component}
ref={ref}
classNames={[
className || "",
validateThis ? "validate-this" : "",
].concat(classNames || [])}
{...inputProps}
/>

{children}
</Div>

{/* INFO SECTION /////////////////////////////////////////////////////////////////////////////////////// */}
{(helpText || errorText) && (
<Div className="info-section vertically-center-items">
{helpText && (
<Element as="span" className="help-text">
{helpText}
</Element>
)}
{errorText && (
<Element as="span" className="error-text">
{errorText}
</Element>
)}
{/* MAIN INPUT ///////////////////////////////////////////////////////////////////////////////////////// */}
<Div data-input-wrapper>
{/* MAIN INPUT */}
<Element<K>
as={Component}
ref={ref}
classNames={[
className || "",
validateThis ? "validate-this" : "",
].concat(classNames || [])}
onChange={onChange}
{...inputProps}
/>
{children}
</Div>
)}
</FormItem>
)

{/* INFO SECTION /////////////////////////////////////////////////////////////////////////////////////// */}
{(helpText || errorText) && (
<Div className="info-section vertically-center-items">
{helpText && (
<Element as="span" className="help-text">
{helpText}
</Element>
)}
{errorText && (
<Element as="span" className="error-text">
{errorText}
</Element>
)}
</Div>
)}
</FormItem>
);
}
) as <K extends InputElementType>(
props: BaseInputComponentWithIconProps<K> & { ref?: React.LegacyRef<InputElementType> }
props: BaseInputComponentWithIconProps<K> & {
ref ? : React.LegacyRef<InputElementType>
}
) => React.ReactElement;

function isFormEvent(event: any): event is FormEvent<any> {
return event && "target" in event && "currentTarget" in event;
}
14 changes: 10 additions & 4 deletions src/components/Form/BaseInputComponent/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { FormEventHandler } from "react";

import { ElementProps } from "../../Element/constants";
import { InputLabelCustomProps } from "../InputLabel/InputLabel";
Expand All @@ -12,6 +12,7 @@ export interface InputCommonProps {
invalid ? : boolean;
}

// INPUT FIELD PROPS ///////////////////////////////////////////////////////////////////////////////////////////////////
// Define allowed combinations for the left side
type LeftSideProps =
| { iconLeft: React.ReactNode; stringLeft ? : never }
Expand All @@ -35,11 +36,16 @@ export type NoSideElements = {
// Combine left and right side constraints
export type InputSideElementProps = LeftSideProps & RightSideProps;

// Base component props including common form input properties
export type FormChangeHandler<K> = FormEventHandler<K>;
export type ValueChangeHandler = (value: string | string[]) => void;
export type FlexibleChangeHandler<K> = FormChangeHandler<K> | ValueChangeHandler;

export type BaseInputComponentProps<K extends {}> =
ElementProps<K> &
Omit<ElementProps<K>, "onChange"> &
InputLabelCustomProps &
InputCommonProps;
InputCommonProps & {
onChange?: FlexibleChangeHandler<K>;
};

// Extended component props including side element constraints
export type BaseInputComponentWithIconProps<K extends {}> =
Expand Down
11 changes: 3 additions & 8 deletions src/components/Form/Checkbox/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,18 @@ import "./switch.css";
// TYPES ===============================================================================================================
import { BaseInputComponentProps } from "../BaseInputComponent/constants";

export interface SwitchCustomProps {
size ? : "small" | "medium" | "large";
}

export type SwitchElementType = HTMLInputElement;
export type SwitchProps = Omit<BaseInputComponentProps<SwitchElementType>, keyof SwitchCustomProps | "as"> &
SwitchCustomProps;
export type SwitchProps = Omit<BaseInputComponentProps<SwitchElementType>, "as">;

// COMPONENT ///////////////////////////////////////////////////////////////////////////////////////////////////////////
export const Switch = React.forwardRef(
({ id, name, value, size = "medium", ...props }: SwitchProps, ref: React.Ref<SwitchElementType>) => {
({ id, name, value, ...props }: SwitchProps, ref: React.Ref<SwitchElementType>) => {
// Use ID as default for name and value if they’re not provided
const derivedName = useMemo(() => name || id, [name, id]);
const derivedValue = useMemo(() => value || id, [value, id]);

return (
<Element<SwitchElementType> as="div" data-switch ref={ref} className={`size-${size}`}>
<Element<SwitchElementType> as="div" data-switch ref={ref}>
<BaseInputComponent
as="input"
type="checkbox"
Expand Down
80 changes: 38 additions & 42 deletions src/components/Form/Checkbox/checkbox.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
}

label {
display : inline-flex;
position : relative;
font-family : var(--paragraph-font);
color : var(--paragraph-text-colour);
cursor : pointer;
line-height : 1;
user-select : none;
display : inline-flex;
position : relative;
font-family : var(--paragraph-font);
color : var(--paragraph-text-colour);
cursor : pointer;
padding-left : 24px;
line-height : 1;
user-select : none;

&::before,
&::after {
Expand All @@ -40,22 +41,12 @@
pointer-events : none;
background : var(--checkbox-square-bg-default);
box-shadow : 0 2px 4px -2px hsla(0, 0%, 0%, 0.24) inset;
}

&:hover label::before { background : var(--checkbox-square-bg-hover); }
input[type="checkbox"]:checked + label::before { background : var(--checkbox-square-bg-checked); }
input[type="checkbox"]:focus + label::before { outline : solid 2px var(--checkbox-square-bg-disabled); }

label::after { opacity : 0; }

/* The square =========================================================== */
label::before {
position : absolute;
height : 16px;
width : 16px;
top : 0;
left : 0;
border-radius : var(--checkbox-square-border-radius);
position : absolute;
height : 16px;
width : 16px;
top : 0;
left : 0;
border-radius : var(--checkbox-square-border-radius);
}

/* The white tick inside ================================================ */
Expand All @@ -65,31 +56,36 @@
width : 12px;
left : 2px;
top : 4px;
opacity : 0;
border-left : 2px solid var(--checkbox-tick);
border-bottom : 2px solid var(--checkbox-tick);
transform : rotate(-45deg);
}

label { padding-left : 24px; }

input[type="checkbox"]:checked + label::after { opacity : 1; }

/* DISABLED ============================================================= */
input[type="checkbox"]:disabled + label,
input[type="checkbox"]:disabled + label::before,
input[type="checkbox"]:disabled + label::after {
filter : grayscale(24%);
user-select : none;
pointer-events : none;
cursor : not-allowed;
}

[data-form-wrapper].spacing-none & { margin-bottom : 0; }
[data-form-wrapper].spacing-nano & { margin-bottom : 8px; }
[data-form-wrapper].spacing-micro & { margin-bottom : 12px; }
[data-form-wrapper].spacing-tiny & { margin-bottom : 16px; }
[data-form-wrapper].spacing-small & { margin-bottom : 24px; }

[data-form-wrapper].spacing-none & { margin-bottom : 0; }
[data-form-wrapper].spacing-nano & { margin-bottom : 8px; }
[data-form-wrapper].spacing-micro & { margin-bottom : 12px; }
[data-form-wrapper].spacing-tiny & { margin-bottom : 16px; }
[data-form-wrapper].spacing-small & { margin-bottom : 24px; }
[data-form-wrapper].spacing-medium & { margin-bottom : 32px; }
[data-form-wrapper].spacing-large & { margin-bottom : 40px; }
[data-form-wrapper].spacing-huge & { margin-bottom : 48px; }
[data-form-wrapper].spacing-large & { margin-bottom : 40px; }
[data-form-wrapper].spacing-huge & { margin-bottom : 48px; }
}


/* HOVER */
[data-form-item]:has(input[type="checkbox"]:hover) > label::before {
background : var(--checkbox-square-bg-hover);
}

/* FOCUS */
[data-form-item]:has(input[type="checkbox"]:focus) > label::before {
outline : solid 2px var(--checkbox-square-bg-disabled);
}

/* CHECKED */
[data-form-item]:has(input[type="checkbox"]:checked) > label::before { background : var(--checkbox-square-bg-checked); }
[data-form-item]:has(input[type="checkbox"]:checked) > label::after { opacity : 1; }
Loading
Loading