Update local testing script and resolve errors
This commit is contained in:
@@ -78,9 +78,7 @@ const AskOrganizerContainer = memo<AskOrganizerProps>(
|
||||
buttonHref,
|
||||
},
|
||||
onContactClick as
|
||||
| ((
|
||||
_data: Record<string, unknown>,
|
||||
) => void)
|
||||
| ((_data: Record<string, unknown>) => void)
|
||||
| undefined,
|
||||
);
|
||||
|
||||
@@ -111,4 +109,3 @@ const AskOrganizerContainer = memo<AskOrganizerProps>(
|
||||
AskOrganizerContainer.displayName = "AskOrganizer";
|
||||
|
||||
export default AskOrganizerContainer;
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ export interface AskOrganizerViewProps {
|
||||
variant: AskOrganizerVariant;
|
||||
labelledBy?: string;
|
||||
onContactClick: (
|
||||
event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>,
|
||||
_event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>,
|
||||
) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,4 +53,3 @@ function AskOrganizerView({
|
||||
}
|
||||
|
||||
export default AskOrganizerView;
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default } from "./AskOrganizer.container";
|
||||
export * from "./AskOrganizer.types";
|
||||
|
||||
|
||||
@@ -34,6 +34,6 @@ export interface CheckboxViewProps {
|
||||
checkGlyphColor: string;
|
||||
labelColor: string;
|
||||
accessibilityProps: React.HTMLAttributes<HTMLSpanElement>;
|
||||
onToggle: (e: React.MouseEvent | React.KeyboardEvent) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLSpanElement>) => void;
|
||||
onToggle: (_e: React.MouseEvent | React.KeyboardEvent) => void;
|
||||
onKeyDown: (_e: React.KeyboardEvent<HTMLSpanElement>) => void;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,7 @@ function ContentContainerView({
|
||||
<h3 className={titleClasses}>{post.frontmatter.title}</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className={descriptionClasses}>
|
||||
{post.frontmatter.description}
|
||||
</p>
|
||||
<p className={descriptionClasses}>{post.frontmatter.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -63,9 +63,7 @@ function ContentLockupView({
|
||||
</div>
|
||||
|
||||
{/* Subtitle */}
|
||||
{subtitle ? (
|
||||
<h2 className={styles.subtitle}>{subtitle}</h2>
|
||||
) : null}
|
||||
{subtitle ? <h2 className={styles.subtitle}>{subtitle}</h2> : null}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
@@ -94,11 +92,7 @@ function ContentLockupView({
|
||||
</div>
|
||||
{/* Large button for md and lg breakpoints */}
|
||||
<div className="hidden md:block xl:hidden">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className={buttonClassName}
|
||||
>
|
||||
<Button variant="primary" size="large" className={buttonClassName}>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface ContextMenuItemProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface ContextMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
selected?: boolean;
|
||||
hasSubmenu?: boolean;
|
||||
@@ -18,6 +17,6 @@ export interface ContextMenuItemViewProps {
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
itemClasses: string;
|
||||
handleClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
handleClick: (_e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
handleKeyDown: (_e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
@@ -61,4 +61,3 @@ const FeatureGridContainer = memo<FeatureGridProps>(
|
||||
FeatureGridContainer.displayName = "FeatureGrid";
|
||||
|
||||
export default FeatureGridContainer;
|
||||
|
||||
|
||||
@@ -17,4 +17,3 @@ export interface FeatureGridViewProps extends FeatureGridProps {
|
||||
features: Feature[];
|
||||
labelledBy?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,4 +50,3 @@ function FeatureGridView({
|
||||
}
|
||||
|
||||
export default FeatureGridView;
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default } from "./FeatureGrid.container";
|
||||
export * from "./FeatureGrid.types";
|
||||
|
||||
|
||||
@@ -31,15 +31,15 @@ export interface HeaderViewProps {
|
||||
| "footerLg";
|
||||
showText: boolean;
|
||||
}>;
|
||||
renderNavigationItems: (size: NavSize) => React.ReactNode;
|
||||
renderLoginButton: (size: NavSize) => React.ReactNode;
|
||||
renderNavigationItems: (_size: NavSize) => React.ReactNode;
|
||||
renderLoginButton: (_size: NavSize) => React.ReactNode;
|
||||
renderCreateRuleButton: (
|
||||
buttonSize: "xsmall" | "small" | "medium" | "large" | "xlarge",
|
||||
containerSize: "small" | "medium" | "large" | "xlarge",
|
||||
avatarSize: "small" | "medium" | "large" | "xlarge",
|
||||
_buttonSize: "xsmall" | "small" | "medium" | "large" | "xlarge",
|
||||
_containerSize: "small" | "medium" | "large" | "xlarge",
|
||||
_avatarSize: "small" | "medium" | "large" | "xlarge",
|
||||
) => React.ReactNode;
|
||||
renderLogo: (
|
||||
size:
|
||||
_size:
|
||||
| "default"
|
||||
| "homeHeaderXsmall"
|
||||
| "homeHeaderSm"
|
||||
@@ -52,7 +52,7 @@ export interface HeaderViewProps {
|
||||
| "headerXl"
|
||||
| "footer"
|
||||
| "footerLg",
|
||||
showText: boolean,
|
||||
_showText: boolean,
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,15 +35,15 @@ export interface HomeHeaderViewProps {
|
||||
| "footerLg";
|
||||
showText: boolean;
|
||||
}>;
|
||||
renderNavigationItems: (size: NavSize) => React.ReactNode;
|
||||
renderLoginButton: (size: NavSize) => React.ReactNode;
|
||||
renderNavigationItems: (_size: NavSize) => React.ReactNode;
|
||||
renderLoginButton: (_size: NavSize) => React.ReactNode;
|
||||
renderCreateRuleButton: (
|
||||
buttonSize: "xsmall" | "small" | "medium" | "large" | "xlarge",
|
||||
containerSize: "small" | "medium" | "large" | "xlarge",
|
||||
avatarSize: "small" | "medium" | "large" | "xlarge",
|
||||
_buttonSize: "xsmall" | "small" | "medium" | "large" | "xlarge",
|
||||
_containerSize: "small" | "medium" | "large" | "xlarge",
|
||||
_avatarSize: "small" | "medium" | "large" | "xlarge",
|
||||
) => React.ReactNode;
|
||||
renderLogo: (
|
||||
size:
|
||||
_size:
|
||||
| "default"
|
||||
| "homeHeaderXsmall"
|
||||
| "homeHeaderSm"
|
||||
@@ -56,6 +56,6 @@ export interface HomeHeaderViewProps {
|
||||
| "headerXl"
|
||||
| "footer"
|
||||
| "footerLg",
|
||||
showText: boolean,
|
||||
_showText: boolean,
|
||||
) => React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface InputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "onFocus" | "onBlur"> {
|
||||
export interface InputProps extends Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
"size" | "onChange" | "onFocus" | "onBlur"
|
||||
> {
|
||||
size?: "small" | "medium" | "large";
|
||||
labelVariant?: "default" | "horizontal";
|
||||
state?: "default" | "active" | "hover" | "focus";
|
||||
@@ -32,7 +34,7 @@ export interface InputViewProps {
|
||||
labelClasses: string;
|
||||
inputClasses: string;
|
||||
borderRadius: string;
|
||||
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleFocus: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
handleBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
handleChange: (_e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleFocus: (_e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
handleBlur: (_e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface MenuBarItemProps
|
||||
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
export interface MenuBarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
variant?: "default" | "home";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface NavigationItemProps
|
||||
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "isActive"> {
|
||||
export interface NavigationItemProps extends Omit<
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
"isActive"
|
||||
> {
|
||||
href?: string;
|
||||
children?: React.ReactNode;
|
||||
variant?: "default";
|
||||
|
||||
@@ -33,4 +33,3 @@ const NumberedCardsContainer = memo<NumberedCardsProps>(
|
||||
NumberedCardsContainer.displayName = "NumberedCards";
|
||||
|
||||
export default NumberedCardsContainer;
|
||||
|
||||
|
||||
@@ -13,4 +13,3 @@ export interface NumberedCardsProps {
|
||||
export interface NumberedCardsViewProps extends NumberedCardsProps {
|
||||
schemaJson: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,4 +63,3 @@ function NumberedCardsView({
|
||||
}
|
||||
|
||||
export default NumberedCardsView;
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default } from "./NumberedCards.container";
|
||||
export * from "./NumberedCards.types";
|
||||
|
||||
|
||||
@@ -41,5 +41,5 @@ export interface QuoteBlockViewProps {
|
||||
imageLoading: boolean;
|
||||
currentAvatarSrc: string;
|
||||
onImageLoad: () => void;
|
||||
onImageError: (error: unknown) => void;
|
||||
onImageError: (_error: unknown) => void;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,6 @@ export interface RadioButtonViewProps {
|
||||
backgroundWhenChecked: string;
|
||||
dotColor: string;
|
||||
labelColor: string;
|
||||
onToggle: (e: React.MouseEvent | React.KeyboardEvent) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLSpanElement>) => void;
|
||||
onToggle: (_e: React.MouseEvent | React.KeyboardEvent) => void;
|
||||
onKeyDown: (_e: React.KeyboardEvent<HTMLSpanElement>) => void;
|
||||
}
|
||||
|
||||
@@ -25,5 +25,5 @@ export interface RadioGroupViewProps {
|
||||
options: RadioOption[];
|
||||
className: string;
|
||||
ariaLabel?: string;
|
||||
onOptionChange: (optionValue: string) => void;
|
||||
onOptionChange: (_optionValue: string) => void;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ export interface RelatedArticlesViewProps {
|
||||
slugOrder: string[];
|
||||
isMobile: boolean;
|
||||
transformStyle: React.CSSProperties;
|
||||
getProgressStyle: (index: number) => React.CSSProperties;
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
getProgressStyle: (_index: number) => React.CSSProperties;
|
||||
onMouseDown?: (_e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ export interface RuleCardViewProps {
|
||||
backgroundColor: string;
|
||||
className: string;
|
||||
onClick: () => void;
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
onKeyDown: (_event: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,12 @@ const RuleStackContainer = memo<RuleStackProps>(({ className = "" }) => {
|
||||
logger.debug(`${templateName} template clicked`);
|
||||
};
|
||||
|
||||
return <RuleStackView className={className} onTemplateClick={handleTemplateClick} />;
|
||||
return (
|
||||
<RuleStackView
|
||||
className={className}
|
||||
onTemplateClick={handleTemplateClick}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
RuleStackContainer.displayName = "RuleStack";
|
||||
|
||||
@@ -4,5 +4,5 @@ export interface RuleStackProps {
|
||||
|
||||
export interface RuleStackViewProps {
|
||||
className: string;
|
||||
onTemplateClick: (templateName: string) => void;
|
||||
onTemplateClick: (_templateName: string) => void;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,10 @@ const SelectContainer = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
|
||||
// Sync internal state with external value prop
|
||||
useEffect(() => {
|
||||
if (value !== undefined) {
|
||||
if (value !== undefined && value !== selectedValue) {
|
||||
setSelectedValue(value);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value]);
|
||||
|
||||
useImperativeHandle(
|
||||
@@ -276,7 +277,6 @@ const SelectContainer = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
labelVariant={labelVariant}
|
||||
className={className}
|
||||
options={options}
|
||||
children={children}
|
||||
selectId={selectId}
|
||||
labelId={labelId}
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -26,8 +26,8 @@ export interface SelectViewProps {
|
||||
chevronClasses: string;
|
||||
// Callbacks
|
||||
onButtonClick: () => void;
|
||||
onButtonKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onOptionClick: (value: string, text: string) => void;
|
||||
onButtonKeyDown: (_e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onOptionClick: (_value: string, _text: string) => void;
|
||||
// Refs
|
||||
selectRef: React.RefObject<HTMLButtonElement>;
|
||||
menuRef: React.RefObject<HTMLDivElement>;
|
||||
@@ -38,11 +38,11 @@ export interface SelectViewProps {
|
||||
|
||||
export function SelectView({
|
||||
label,
|
||||
placeholder,
|
||||
placeholder: _placeholder,
|
||||
size,
|
||||
disabled,
|
||||
error,
|
||||
labelVariant,
|
||||
error: _error,
|
||||
labelVariant: _labelVariant,
|
||||
options,
|
||||
children,
|
||||
selectId,
|
||||
@@ -118,9 +118,7 @@ export function SelectView({
|
||||
key={option.value}
|
||||
selected={option.value === selectedValue}
|
||||
size={size}
|
||||
onClick={() =>
|
||||
onOptionClick(option.value, option.label)
|
||||
}
|
||||
onClick={() => onOptionClick(option.value, option.label)}
|
||||
>
|
||||
{option.label}
|
||||
</SelectOption>
|
||||
|
||||
@@ -15,6 +15,6 @@ export interface SelectOptionViewProps {
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
itemClasses: string;
|
||||
handleClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
handleClick: (_e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
handleKeyDown: (_e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { forwardRef } from "react";
|
||||
import type { SelectOptionViewProps } from "./SelectOption.types";
|
||||
|
||||
export const SelectOptionView = forwardRef<HTMLDivElement, SelectOptionViewProps>(
|
||||
export const SelectOptionView = forwardRef<
|
||||
HTMLDivElement,
|
||||
SelectOptionViewProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface SwitchProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
|
||||
export interface SwitchProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"onChange"
|
||||
> {
|
||||
checked?: boolean;
|
||||
onChange?: (
|
||||
_e:
|
||||
@@ -23,8 +25,8 @@ export interface SwitchViewProps {
|
||||
trackClasses: string;
|
||||
thumbClasses: string;
|
||||
labelClasses: string;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onFocus: (e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur: (e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onClick: (_e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onKeyDown: (_e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onFocus: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface TextAreaProps
|
||||
extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size" | "onChange" | "onFocus" | "onBlur"> {
|
||||
export interface TextAreaProps extends Omit<
|
||||
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||
"size" | "onChange" | "onFocus" | "onBlur"
|
||||
> {
|
||||
size?: "small" | "medium" | "large";
|
||||
labelVariant?: "default" | "horizontal";
|
||||
state?: "default" | "active" | "hover" | "focus";
|
||||
@@ -33,7 +35,7 @@ export interface TextAreaViewProps {
|
||||
labelClasses: string;
|
||||
textareaClasses: string;
|
||||
borderRadius: string;
|
||||
handleChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
handleFocus: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
|
||||
handleBlur: (e: React.FocusEvent<HTMLTextAreaElement>) => void;
|
||||
handleChange: (_e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
handleFocus: (_e: React.FocusEvent<HTMLTextAreaElement>) => void;
|
||||
handleBlur: (_e: React.FocusEvent<HTMLTextAreaElement>) => void;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const TextAreaView = forwardRef<HTMLTextAreaElement, TextAreaViewProps>(
|
||||
value,
|
||||
name,
|
||||
disabled,
|
||||
className,
|
||||
className: _className,
|
||||
rows,
|
||||
containerClasses,
|
||||
labelClasses,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface ToggleProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
|
||||
export interface ToggleProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"onChange"
|
||||
> {
|
||||
label?: string;
|
||||
checked?: boolean;
|
||||
onChange?: (
|
||||
@@ -34,11 +36,11 @@ export interface ToggleViewProps {
|
||||
labelClasses: string;
|
||||
toggleClasses: string;
|
||||
onClick: (
|
||||
e:
|
||||
_e:
|
||||
| React.MouseEvent<HTMLButtonElement>
|
||||
| React.KeyboardEvent<HTMLButtonElement>,
|
||||
) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onFocus: (e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur: (e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onKeyDown: (_e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onFocus: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,6 @@ const ToggleGroupContainer = memo(
|
||||
return (
|
||||
<ToggleGroupView
|
||||
groupId={groupId}
|
||||
children={children}
|
||||
className={className}
|
||||
position={position}
|
||||
state={state}
|
||||
@@ -131,7 +130,9 @@ const ToggleGroupContainer = memo(
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
{...rest}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupView>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface ToggleGroupProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
|
||||
export interface ToggleGroupProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"onChange"
|
||||
> {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
position?: "left" | "middle" | "right";
|
||||
@@ -24,8 +26,8 @@ export interface ToggleGroupViewProps {
|
||||
showText: boolean;
|
||||
ariaLabel?: string;
|
||||
toggleClasses: string;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onFocus: (e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur: (e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onClick: (_e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onKeyDown: (_e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
||||
onFocus: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
onBlur: (_e: React.FocusEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { ToggleGroupViewProps } from "./ToggleGroup.types";
|
||||
export function ToggleGroupView({
|
||||
groupId,
|
||||
children,
|
||||
className,
|
||||
position,
|
||||
state,
|
||||
className: _className,
|
||||
position: _position,
|
||||
state: _state,
|
||||
showText,
|
||||
ariaLabel,
|
||||
toggleClasses,
|
||||
|
||||
@@ -96,11 +96,14 @@ const WebVitalsDashboardContainer = memo(() => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<WebVitalsDashboardView vitals={vitals} metrics={metrics} loading={loading} />
|
||||
<WebVitalsDashboardView
|
||||
vitals={vitals}
|
||||
metrics={metrics}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
WebVitalsDashboardContainer.displayName = "WebVitalsDashboard";
|
||||
|
||||
export default WebVitalsDashboardContainer;
|
||||
|
||||
|
||||
@@ -31,4 +31,3 @@ export interface WebVitalsDashboardViewProps {
|
||||
metrics: Metrics;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,9 +111,7 @@ function WebVitalsDashboardView({
|
||||
<span className="text-yellow-600">
|
||||
Needs Improvement: {data.needsImprovementCount}
|
||||
</span>
|
||||
<span className="text-red-600">
|
||||
Poor: {data.poorCount}
|
||||
</span>
|
||||
<span className="text-red-600">Poor: {data.poorCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,4 +153,3 @@ function WebVitalsDashboardView({
|
||||
}
|
||||
|
||||
export default WebVitalsDashboardView;
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default } from "./WebVitalsDashboard.container";
|
||||
export * from "./WebVitalsDashboard.types";
|
||||
|
||||
|
||||
+16
-13
@@ -33,27 +33,30 @@ export function useMediaQuery(
|
||||
query: string | keyof typeof BREAKPOINTS,
|
||||
direction: "min" | "max" = "min",
|
||||
): boolean {
|
||||
const [matches, setMatches] = useState(false);
|
||||
// Convert breakpoint key to media query string
|
||||
let mediaQuery: string;
|
||||
if (query in BREAKPOINTS) {
|
||||
const breakpoint = BREAKPOINTS[query as keyof typeof BREAKPOINTS];
|
||||
mediaQuery = `(${direction}-width: ${breakpoint}px)`;
|
||||
} else {
|
||||
mediaQuery = query;
|
||||
}
|
||||
|
||||
// Initialize state with current match if available (SSR safety)
|
||||
const [matches, setMatches] = useState(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return false;
|
||||
}
|
||||
return window.matchMedia(mediaQuery).matches;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Convert breakpoint key to media query string
|
||||
let mediaQuery: string;
|
||||
if (query in BREAKPOINTS) {
|
||||
const breakpoint = BREAKPOINTS[query as keyof typeof BREAKPOINTS];
|
||||
mediaQuery = `(${direction}-width: ${breakpoint}px)`;
|
||||
} else {
|
||||
mediaQuery = query;
|
||||
}
|
||||
|
||||
// Check if window is available (SSR safety)
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const media = window.matchMedia(mediaQuery);
|
||||
// Initialize matches synchronously - this is safe for media queries
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
setMatches(media.matches);
|
||||
|
||||
// Create listener for changes
|
||||
const listener = (event: MediaQueryListEvent) => {
|
||||
|
||||
@@ -16,6 +16,7 @@ The Container/Presentation pattern separates component logic from presentation,
|
||||
### When to Use
|
||||
|
||||
Use this pattern for components that have:
|
||||
|
||||
- Business logic or state management
|
||||
- Data fetching or API calls
|
||||
- Analytics tracking
|
||||
@@ -40,6 +41,7 @@ app/components/[ComponentName]/
|
||||
### File Responsibilities
|
||||
|
||||
#### `index.tsx`
|
||||
|
||||
- Exports the container component as the default export
|
||||
- Optionally exports types for external use
|
||||
- Maintains backward compatibility with existing import paths
|
||||
@@ -50,7 +52,9 @@ export type { AskOrganizerProps } from "./AskOrganizer.types";
|
||||
```
|
||||
|
||||
#### `[ComponentName].container.tsx`
|
||||
|
||||
**Contains all logic:**
|
||||
|
||||
- React hooks (`useState`, `useEffect`, custom hooks)
|
||||
- Event handlers and business logic
|
||||
- Data fetching and API calls
|
||||
@@ -60,6 +64,7 @@ export type { AskOrganizerProps } from "./AskOrganizer.types";
|
||||
- Side effects
|
||||
|
||||
**Should NOT contain:**
|
||||
|
||||
- JSX layout details (beyond composing the view)
|
||||
- Inline styles or complex className logic (pass as props)
|
||||
- Direct DOM manipulation
|
||||
@@ -100,13 +105,16 @@ export default memo(AskOrganizerContainer);
|
||||
```
|
||||
|
||||
#### `[ComponentName].view.tsx`
|
||||
|
||||
**Pure presentation:**
|
||||
|
||||
- Receives all data via props
|
||||
- Renders JSX based on props
|
||||
- No hooks, no state, no side effects
|
||||
- Only imports other presentational components
|
||||
|
||||
**Should NOT contain:**
|
||||
|
||||
- `useState`, `useEffect`, or any hooks
|
||||
- Event handler implementations (receive as callbacks)
|
||||
- Data fetching or API calls
|
||||
@@ -148,6 +156,7 @@ export function AskOrganizerView({
|
||||
```
|
||||
|
||||
#### `[ComponentName].types.ts`
|
||||
|
||||
- Shared TypeScript interfaces and types
|
||||
- Public props interface (used by consumers)
|
||||
- Internal view props (used between container and view)
|
||||
@@ -177,6 +186,7 @@ export interface AskOrganizerViewProps extends AskOrganizerProps {
|
||||
### Container Components
|
||||
|
||||
✅ **DO:**
|
||||
|
||||
- Use React hooks (`useState`, `useEffect`, custom hooks)
|
||||
- Handle all event handlers and business logic
|
||||
- Fetch data and manage loading states
|
||||
@@ -185,6 +195,7 @@ export interface AskOrganizerViewProps extends AskOrganizerProps {
|
||||
- Compose the view component with computed props
|
||||
|
||||
❌ **DON'T:**
|
||||
|
||||
- Include complex JSX layout (delegate to view)
|
||||
- Mix presentation logic with business logic
|
||||
- Access DOM directly (use refs when necessary)
|
||||
@@ -192,6 +203,7 @@ export interface AskOrganizerViewProps extends AskOrganizerProps {
|
||||
### View Components
|
||||
|
||||
✅ **DO:**
|
||||
|
||||
- Receive all data via props
|
||||
- Render JSX based on props
|
||||
- Import only presentational components
|
||||
@@ -199,6 +211,7 @@ export interface AskOrganizerViewProps extends AskOrganizerProps {
|
||||
- Accept callback props for user interactions
|
||||
|
||||
❌ **DON'T:**
|
||||
|
||||
- Use any React hooks
|
||||
- Manage state or side effects
|
||||
- Fetch data or make API calls
|
||||
@@ -237,6 +250,7 @@ const AskOrganizer = memo(({ title, variant, ...props }) => {
|
||||
### After (Container/Presentation)
|
||||
|
||||
**AskOrganizer.container.tsx:**
|
||||
|
||||
```typescript
|
||||
"use client";
|
||||
|
||||
@@ -259,6 +273,7 @@ export default memo(AskOrganizerContainer);
|
||||
```
|
||||
|
||||
**AskOrganizer.view.tsx:**
|
||||
|
||||
```typescript
|
||||
import ContentLockup from "../ContentLockup";
|
||||
import Button from "../Button";
|
||||
@@ -346,6 +361,7 @@ These components serve as reference implementations for the pattern.
|
||||
The following components are candidates for future conversion:
|
||||
|
||||
### High Priority (Complex Logic)
|
||||
|
||||
- `Header` / `HomeHeader` - Navigation state, conditional rendering logic
|
||||
- `MenuBar` - Menu state management, keyboard navigation
|
||||
- `ContextMenu` - Positioning logic, click outside handling
|
||||
@@ -353,12 +369,14 @@ The following components are candidates for future conversion:
|
||||
- `ToggleGroup` - Group state management
|
||||
|
||||
### Medium Priority (Some Logic)
|
||||
|
||||
- `ContentContainer` - Data fetching or transformation
|
||||
- `RelatedArticles` - Data fetching, filtering logic
|
||||
- `RuleStack` - Complex rendering logic
|
||||
- `LogoWall` - Animation or interaction logic
|
||||
|
||||
### Low Priority (Mostly Presentational)
|
||||
|
||||
- `Button`, `Avatar`, `Checkbox`, `Input`, `TextArea` - Simple presentational components
|
||||
- `Separator`, `SectionHeader`, `SectionNumber` - Pure presentation
|
||||
- `QuoteBlock`, `QuoteDecor`, `HeroDecor` - Decorative components
|
||||
|
||||
+60
-1
@@ -3,9 +3,37 @@
|
||||
# Local testing script - run before committing/merging
|
||||
# Usage: ./scripts/test-local.sh
|
||||
|
||||
set -euo pipefail # Exit on error, undefined vars, pipe failures
|
||||
|
||||
echo "🧪 Running local tests before commit..."
|
||||
echo ""
|
||||
|
||||
# Cleanup function to ensure servers are killed
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🧹 Cleaning up any running servers..."
|
||||
# Kill any Next.js servers on common test ports
|
||||
for port in 3000 3010; do
|
||||
if lsof -ti:$port >/dev/null 2>&1; then
|
||||
echo " Killing process on port $port..."
|
||||
lsof -ti:$port | xargs kill -9 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
# Kill any processes from PID files if they exist
|
||||
for pidfile in .next/runner.pid .next/performance-server.pid; do
|
||||
if [ -f "$pidfile" ]; then
|
||||
PID=$(cat "$pidfile" 2>/dev/null || echo "")
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo " Killing server PID: $PID (from $pidfile)..."
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$pidfile"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo "🔍 Linting..."
|
||||
npm run lint || exit 1
|
||||
|
||||
@@ -23,11 +51,42 @@ npm run test:e2e || exit 1
|
||||
|
||||
echo ""
|
||||
echo "🖼️ Visual regression tests..."
|
||||
# Visual tests use Playwright's webServer config, should auto-start server
|
||||
npm run visual:test || exit 1
|
||||
|
||||
echo ""
|
||||
echo "⚡ Performance tests (Lighthouse CI)..."
|
||||
npm run performance:budget || exit 1
|
||||
# Performance tests need a server running on port 3010
|
||||
# Check if server is already running, if not start one
|
||||
if ! lsof -ti:3010 >/dev/null 2>&1; then
|
||||
echo " Starting server for performance tests..."
|
||||
npm run build || exit 1
|
||||
PORT=3010 HOST=127.0.0.1 node node_modules/next/dist/bin/next start -p 3010 -H 127.0.0.1 > .next/performance-server.log 2>&1 &
|
||||
SERVER_PID=$!
|
||||
echo "$SERVER_PID" > .next/performance-server.pid
|
||||
echo " Server started (PID: $SERVER_PID), waiting for readiness..."
|
||||
npx wait-on -t 120000 "tcp:127.0.0.1:3010" || { echo "❌ Server failed to start"; kill $SERVER_PID 2>/dev/null || true; exit 1; }
|
||||
echo " ✅ Server ready"
|
||||
fi
|
||||
|
||||
# Run performance tests
|
||||
npm run performance:budget || EXIT_CODE=$?
|
||||
|
||||
# Cleanup performance server if we started it
|
||||
if [ -f .next/performance-server.pid ]; then
|
||||
PID=$(cat .next/performance-server.pid 2>/dev/null || echo "")
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo " Stopping performance test server..."
|
||||
kill "$PID" 2>/dev/null || true
|
||||
sleep 2
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
fi
|
||||
rm -f .next/performance-server.pid
|
||||
fi
|
||||
|
||||
if [ -n "${EXIT_CODE:-}" ]; then
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All tests passed! Safe to commit/merge."
|
||||
|
||||
Reference in New Issue
Block a user