Skip to content

Commit

Permalink
Add status prop and rename state prop to stage for steps in Ste…
Browse files Browse the repository at this point in the history
…ppedTracker (#3584)
  • Loading branch information
tomhazledine authored Jul 26, 2024
1 parent e76e5b9 commit ffc0dd4
Show file tree
Hide file tree
Showing 13 changed files with 355 additions and 128 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-plants-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@salt-ds/lab": minor
---

Replaced TrackerStep's `state` prop with new `stage` and `status` props. Valid `stage` values are `"pending"` and `"completed"`. Valid `status` values are `"error"` and `"warning"`.
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe("GIVEN a SteppedTracker", () => {
{labels.map((label, key) => (
<TrackerStep
key={key}
state={key === completedStep ? "completed" : undefined}
stage={key === completedStep ? "completed" : undefined}
>
<StepLabel>{label}</StepLabel>
</TrackerStep>
Expand All @@ -114,7 +114,7 @@ describe("GIVEN a SteppedTracker", () => {
.should("not.exist");
});

it("should show completed icon if a state is completed and active", () => {
it("should show completed icon if stage prop is completed and step is active", () => {
const labels = ["Step 1", "Step 2", "Step 3"];

const stepNum = 1;
Expand All @@ -124,7 +124,7 @@ describe("GIVEN a SteppedTracker", () => {
{labels.map((label, key) => (
<TrackerStep
key={key}
state={key === stepNum ? "completed" : undefined}
stage={key === stepNum ? "completed" : undefined}
>
<StepLabel>{label}</StepLabel>
</TrackerStep>
Expand All @@ -143,4 +143,92 @@ describe("GIVEN a SteppedTracker", () => {
.findByTestId("StepActiveIcon")
.should("not.exist");
});

it("should show warning icon if status prop is warning", () => {
const labels = ["Step 1", "Step 2", "Step 3"];

const TestComponent = (
<SteppedTracker activeStep={0} style={{ width: 300 }}>
{labels.map((label, key) => (
<TrackerStep key={key} status={key === 1 ? "warning" : undefined}>
<StepLabel>{label}</StepLabel>
</TrackerStep>
))}
</SteppedTracker>
);

cy.mount(TestComponent);

cy.findAllByRole("listitem")
.filter(`:nth-child(${2})`)
.findByTestId("WarningSolidIcon")
.should("exist");
});

it("should show completed icon if status prop is warning but stage prop is completed", () => {
const labels = ["Step 1", "Step 2", "Step 3"];

const TestComponent = (
<SteppedTracker activeStep={0} style={{ width: 300 }}>
{labels.map((label, key) => (
<TrackerStep
key={key}
stage={key <= 1 ? "completed" : undefined}
status={key === 1 ? "warning" : undefined}
>
<StepLabel>{label}</StepLabel>
</TrackerStep>
))}
</SteppedTracker>
);

cy.mount(TestComponent);

cy.findAllByRole("listitem")
.filter(`:nth-child(${2})`)
.findByTestId("StepSuccessIcon")
.should("exist");
});

it("should show active icon if status prop is warning but step is active", () => {
const labels = ["Step 1", "Step 2", "Step 3"];

const TestComponent = (
<SteppedTracker activeStep={1} style={{ width: 300 }}>
{labels.map((label, key) => (
<TrackerStep key={key} status={key === 1 ? "warning" : undefined}>
<StepLabel>{label}</StepLabel>
</TrackerStep>
))}
</SteppedTracker>
);

cy.mount(TestComponent);

cy.findAllByRole("listitem")
.filter(`:nth-child(${2})`)
.findByTestId("StepActiveIcon")
.should("exist");
});

it("should show error icon if status prop is error", () => {
const labels = ["Step 1", "Step 2", "Step 3"];

const TestComponent = (
<SteppedTracker activeStep={0} style={{ width: 300 }}>
{labels.map((label, key) => (
<TrackerStep key={key} status={key === 1 ? "error" : undefined}>
<StepLabel>{label}</StepLabel>
</TrackerStep>
))}
</SteppedTracker>
);

cy.mount(TestComponent);

cy.findAllByRole("listitem")
.filter(`:nth-child(${2})`)
.findByTestId("ErrorSolidIcon")
.should("exist");
});
});
12 changes: 11 additions & 1 deletion packages/lab/src/stepped-tracker/TrackerStep/TrackerStep.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.saltTrackerStep {
--trackerStep-icon-color-active: var(--saltTrackerStep-icon-color-active, var(--salt-status-info-foreground-decorative));
--trackerStep-icon-color-completed: var(--saltTrackerStep-icon-color-completed, var(--salt-status-success-foreground-decorative));
--trackerStep-icon-color-warning: var(--saltTrackerStep-icon-color-warning, var(--salt-status-warning-foreground-decorative));
--trackerStep-icon-color-error: var(--saltTrackerStep-icon-color-error, var(--salt-status-error-foreground-decorative));

--trackerStep-icon-color: var(--saltTrackerStep-icon-color, var(--salt-status-static-foreground));

Expand Down Expand Up @@ -50,10 +52,18 @@
flex-direction: column;
}

.saltTrackerStep.saltTrackerStep-status-warning {
--trackerStep-icon-color: var(--trackerStep-icon-color-warning);
}

.saltTrackerStep.saltTrackerStep-status-error {
--trackerStep-icon-color: var(--trackerStep-icon-color-error);
}

.saltTrackerStep.saltTrackerStep-active {
--trackerStep-icon-color: var(--trackerStep-icon-color-active);
}

.saltTrackerStep.saltTrackerStep-completed {
.saltTrackerStep.saltTrackerStep-stage-completed {
--trackerStep-icon-color: var(--trackerStep-icon-color-completed);
}
82 changes: 44 additions & 38 deletions packages/lab/src/stepped-tracker/TrackerStep/TrackerStep.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { makePrefixer } from "@salt-ds/core";
import { type ValidationStatus, makePrefixer } from "@salt-ds/core";
import {
ErrorSolidIcon,
StepActiveIcon,
StepDefaultIcon,
StepSuccessIcon,
WarningSolidIcon,
} from "@salt-ds/icons";
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
Expand All @@ -19,46 +21,28 @@ import trackerStepCss from "./TrackerStep.css";

const withBaseName = makePrefixer("saltTrackerStep");

type State = "default" | "completed";

type StateWithActive = State | "active";
type StageOptions = "pending" | "completed";
type StatusOptions = Extract<ValidationStatus, "warning" | "error">;

export interface TrackerStepProps extends ComponentPropsWithoutRef<"li"> {
/**
* The state of the TrackerStep
* The stage of the step: "pending" or "completed" (note, "active" is derived from "activeStep" in parent SteppedTracker component)
*/
stage?: StageOptions;
/**
* The status of the step: warning or error
*
* If the stage is completed or active, the status prop will be ignored
*/
state?: State;
status?: StatusOptions;
}

const iconMap = {
default: StepDefaultIcon,
pending: StepDefaultIcon,
active: StepActiveIcon,
completed: StepSuccessIcon,
};

const getStateIcon = ({
isActive,
state,
}: {
isActive: boolean;
state: State;
}) => {
if (state === "default" && isActive) {
return StepActiveIcon;
}
return iconMap[state];
};

const getState = ({
isActive,
state,
}: {
isActive: boolean;
state: State;
}): StateWithActive => {
if (state === "default" && isActive) {
return "active";
}
return state;
warning: WarningSolidIcon,
error: ErrorSolidIcon,
};

const useCheckWithinSteppedTracker = (isWithinSteppedTracker: boolean) => {
Expand All @@ -73,10 +57,26 @@ const useCheckWithinSteppedTracker = (isWithinSteppedTracker: boolean) => {
}, [isWithinSteppedTracker]);
};

const parseIconName = ({
stage,
status,
active,
}: {
stage: StageOptions;
status?: StatusOptions;
active: boolean;
}) => {
if (stage === "completed") return "completed";
if (active) return "active";
if (status) return status;
return stage;
};

export const TrackerStep = forwardRef<HTMLLIElement, TrackerStepProps>(
function TrackerStep(props, ref) {
const {
state = "default",
stage = "pending",
status,
style,
className,
children,
Expand All @@ -97,8 +97,9 @@ export const TrackerStep = forwardRef<HTMLLIElement, TrackerStepProps>(
useCheckWithinSteppedTracker(isWithinSteppedTracker);

const isActive = activeStep === stepNumber;
const Icon = getStateIcon({ isActive, state });
const resolvedState = getState({ isActive, state });
const iconName = parseIconName({ stage, status, active: isActive });

const Icon = iconMap[iconName];
const connectorState = activeStep > stepNumber ? "active" : "default";
const hasConnector = stepNumber < totalSteps - 1;

Expand All @@ -109,10 +110,15 @@ export const TrackerStep = forwardRef<HTMLLIElement, TrackerStepProps>(

return (
<li
className={clsx(withBaseName(), withBaseName(resolvedState), className)}
className={clsx(
withBaseName(),
withBaseName(`stage-${stage}`),
withBaseName(`status-${status}`),
{ [withBaseName("active")]: isActive },
className,
)}
style={innerStyle}
aria-current={isActive ? "step" : undefined}
data-state={state}
ref={ref}
{...restProps}
>
Expand Down
Loading

0 comments on commit ffc0dd4

Please sign in to comment.