diff --git a/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.stories.tsx b/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.stories.tsx index 04f853db..2d8416c8 100644 --- a/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.stories.tsx +++ b/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.stories.tsx @@ -3,11 +3,12 @@ import { Meta, StoryObj } from "@storybook/react"; import { InternalShadowRectButton } from "./InternalShadowRectButton"; -import { oakIconNames } from "@/components/atoms/OakIcon"; +import { OakIcon, oakIconNames } from "@/components/atoms/OakIcon"; import { OakFlex, OakLI, OakUL } from "@/components/atoms"; import { borderArgTypes } from "@/storybook-helpers/borderStyleHelpers"; import { colorArgTypes } from "@/storybook-helpers/colorStyleHelpers"; import { sizeArgTypes } from "@/storybook-helpers/sizeStyleHelpers"; +import { flexArgTypes } from "@/storybook-helpers/flexStyleHelpers"; const controlIconNames = [null, [...oakIconNames].sort()].flat(); @@ -20,6 +21,8 @@ const meta: Meta = { options: controlIconNames, control: { type: "select" }, }, + iconLayout: flexArgTypes["$flexDirection"], + iconGap: flexArgTypes["$gap"], isTrailingIcon: { control: { type: "boolean" }, }, @@ -42,6 +45,9 @@ const meta: Meta = { controls: { include: [ "iconName", + "iconOverride", + "iconLayout", + "iconGap", "isTrailingIcon", "isLoading", "defaultBackground", @@ -161,3 +167,58 @@ export const ButtonInList: Story = { disabledTextColor: "text-disabled", }, }; + +export const VeritcalLayout: Story = { + render: (args) => ( + + Button + + ), + args: { + iconName: "bell", + iconLayout: "column", + defaultBackground: "bg-btn-secondary", + defaultTextColor: "text-primary", + defaultBorderColor: "text-primary", + hoverBackground: "bg-btn-secondary-hover", + hoverTextColor: "text-primary", + hoverBorderColor: "text-primary", + disabledBackground: "bg-btn-secondary-disabled", + disabledBorderColor: "text-disabled", + disabledTextColor: "text-disabled", + }, +}; + +export const CustomIcon: Story = { + render: (args) => { + const customIcon = ( + + ); + return ( + + + Button + + + ); + }, + args: { + iconLayout: "column", + iconGap: "space-between-m", + font: "heading-5", + defaultBackground: "bg-btn-secondary", + defaultTextColor: "text-primary", + defaultBorderColor: "text-primary", + hoverBackground: "bg-btn-secondary-hover", + hoverTextColor: "text-primary", + hoverBorderColor: "text-primary", + disabledBackground: "bg-btn-secondary-disabled", + disabledBorderColor: "text-disabled", + disabledTextColor: "text-disabled", + }, +}; diff --git a/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.tsx b/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.tsx index e271d36b..43ede054 100644 --- a/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.tsx +++ b/src/components/molecules/InternalShadowRectButton/InternalShadowRectButton.tsx @@ -17,6 +17,9 @@ import { parseColor } from "@/styles/helpers/parseColor"; import { OakCombinedColorToken, OakDropShadowToken } from "@/styles"; import { SizeStyleProps, sizeStyle } from "@/styles/utils/sizeStyle"; import { PolymorphicPropsWithoutRef } from "@/components/polymorphic"; +import { SpacingStyleProps } from "@/styles/utils/spacingStyle"; +import { FlexStyleProps } from "@/styles/utils/flexStyle"; +import { TypographyStyleProps } from "@/styles/utils/typographyStyle"; export type InternalShadowRectButtonProps = Omit< InternalButtonProps, @@ -30,7 +33,19 @@ export type InternalShadowRectButtonProps = Omit< | "$color" > & { iconName?: OakIconName; + /** + * we can set a custom icon if we want different sizes and padding + */ + iconOverride?: React.ReactNode; isTrailingIcon?: boolean; + /** + * we can arrange the icon vertically or horizontally + */ + iconLayout?: FlexStyleProps["$flexDirection"]; + /** + * we can adjust the gap between the icon and the text + */ + iconGap?: FlexStyleProps["$gap"]; defaultTextColor: OakCombinedColorToken; defaultBackground: OakCombinedColorToken; defaultBorderColor: OakCombinedColorToken; @@ -43,6 +58,9 @@ export type InternalShadowRectButtonProps = Omit< width?: SizeStyleProps["$width"]; maxWidth?: SizeStyleProps["$maxWidth"]; hoverShadow?: OakDropShadowToken | null; + pv?: SpacingStyleProps["$pv"]; + ph?: SpacingStyleProps["$ph"]; + font?: TypographyStyleProps["$font"]; } & PositionStyleProps; const StyledInternalButton = styled(InternalButton)< @@ -144,10 +162,16 @@ export const InternalShadowRectButton = ( disabledBorderColor, className, hoverShadow = "drop-shadow-lemon", + pv = "inner-padding-s", + ph = "inner-padding-m", + iconLayout = "row", + iconGap = "space-between-ssx", + iconOverride, + font = "heading-7", ...rest } = props; - const icon = ( + const icon = iconOverride ?? ( <> {iconName && ( ( $background={defaultBackground} $borderColor={defaultBorderColor} $color={defaultTextColor} - $pv={"inner-padding-s"} - $ph={"inner-padding-m"} + $pv={pv} + $ph={ph} $borderRadius={"border-radius-s"} $position={"relative"} disabled={disabled || isLoading} @@ -220,13 +244,13 @@ export const InternalShadowRectButton = ( {...rest} > {!isTrailingIcon && iconLogic} - {children} + {children} {isTrailingIcon && iconLogic} diff --git a/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.stories.tsx b/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.stories.tsx new file mode 100644 index 00000000..e2d10c6b --- /dev/null +++ b/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.stories.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { StoryObj, Meta } from "@storybook/react"; + +import { OakPupilJourneyYearButton } from "./OakPupilJourneyYearButton"; + +const meta: Meta = { + component: OakPupilJourneyYearButton, + tags: ["autodocs"], + argTypes: { + phase: { control: { type: "radio" }, options: ["primary", "secondary"] }, + disabled: { control: { type: "boolean" } }, + }, + parameters: { + controls: { + include: ["phase", "disabled"], + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ( + Year 1 + ), + args: { phase: "primary", disabled: false }, +}; + +export const Disabled: Story = { + render: (args) => ( + Year 1 + ), + args: { disabled: true }, +}; diff --git a/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.test.tsx b/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.test.tsx new file mode 100644 index 00000000..ac9c6dbf --- /dev/null +++ b/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.test.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import "@testing-library/jest-dom"; +import { create } from "react-test-renderer"; + +import { OakPupilJourneyYearButton } from "./OakPupilJourneyYearButton"; + +import renderWithTheme from "@/test-helpers/renderWithTheme"; +import { OakThemeProvider } from "@/components/atoms"; +import { oakDefaultTheme } from "@/styles"; + +describe("OakPupilJourneyYearButton", () => { + it("renders", () => { + const { getByRole } = renderWithTheme( + + Year 1 + , + ); + expect(getByRole("button", { name: "Year 1" })).toBeInTheDocument(); + }); + + it("matches snapshot", () => { + const tree = create( + + + Year 1 + + , + ).toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.tsx b/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.tsx new file mode 100644 index 00000000..1715241a --- /dev/null +++ b/src/components/organisms/pupil/OakPupilJourneyYearButton/OakPupilJourneyYearButton.tsx @@ -0,0 +1,76 @@ +import React, { ElementType } from "react"; + +import { + InternalShadowRectButton, + InternalShadowRectButtonProps, +} from "@/components/molecules/InternalShadowRectButton"; +import { PolymorphicPropsWithoutRef } from "@/components/polymorphic"; + +export type OakPupilJourneyYearButtonProps = { + phase: "primary" | "secondary"; +} & Omit< + InternalShadowRectButtonProps, + | "defaultBorderColor" + | "defaultBackground" + | "defaultTextColor" + | "hoverBackground" + | "hoverBorderColor" + | "hoverTextColor" + | "disabledBackground" + | "disabledBorderColor" + | "disabledTextColor" + | "pv" + | "ph" + | "font" +>; + +/** + * + * A specific implementation of InternalRectButton + * + * Changes colour according to the phase prop. Can be used as a link or a button. + * The following callbacks are available for tracking focus events: + * + * ### onClick + * `onClick?: (event: React.MouseEvent) => void;` + * + * ### onHovered + * `onHovered?: (event: React.MouseEvent, duration: number) => void;`
+ * called after a mouseEnter and mouseLeave event has happened + */ + +export const OakPupilJourneyYearButton = ({ + phase, + element, + ...rest +}: OakPupilJourneyYearButtonProps & PolymorphicPropsWithoutRef) => { + const defaultBackground = + phase === "primary" + ? "bg-decorative4-very-subdued" + : "bg-decorative3-very-subdued"; + const hoverBackground = + phase === "primary" ? "bg-decorative4-main" : "bg-decorative3-main"; + const borderColor = + phase === "primary" + ? "border-decorative4-stronger" + : "border-decorative3-stronger"; + + return ( + + ); +}; diff --git a/src/components/organisms/pupil/OakPupilJourneyYearButton/__snapshots__/OakPupilJourneyYearButton.test.tsx.snap b/src/components/organisms/pupil/OakPupilJourneyYearButton/__snapshots__/OakPupilJourneyYearButton.test.tsx.snap new file mode 100644 index 00000000..f6f0b828 --- /dev/null +++ b/src/components/organisms/pupil/OakPupilJourneyYearButton/__snapshots__/OakPupilJourneyYearButton.test.tsx.snap @@ -0,0 +1,160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`OakPupilJourneyYearButton matches snapshot 1`] = ` +.c0 { + position: relative; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + font-family: Lexend,sans-serif; +} + +.c2 { + position: absolute; + top: 0rem; + width: 100%; + height: 100%; + border-radius: 0.25rem; + font-family: Lexend,sans-serif; +} + +.c5 { + font-family: Lexend,sans-serif; +} + +.c6 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + gap: 0.5rem; +} + +.c7 { + font-family: Lexend,sans-serif; + font-weight: 600; + font-size: 1.25rem; + line-height: 1.5rem; + -webkit-letter-spacing: 0.0115rem; + -moz-letter-spacing: 0.0115rem; + -ms-letter-spacing: 0.0115rem; + letter-spacing: 0.0115rem; +} + +.c3 { + background: none; + color: inherit; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + text-align: left; + font-family: unset; + outline: none; + font-family: Lexend,sans-serif; + color: #222222; + background: #f5e9f2; + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 1.5rem; + padding-bottom: 1.5rem; + border: 0.125rem solid; + border-color: #cf9cc3; + border-radius: 0.25rem; +} + +.c3:disabled { + pointer-events: none; + cursor: default; +} + +.c4 { + position: relative; + width: 100%; + height: 100%; + display: inline-block; +} + +.c4:hover { + -webkit-text-decoration: underline; + text-decoration: underline; + color: #222222; + background: #deb7d5; + border-color: #cf9cc3; +} + +.c4:active { + background: #f5e9f2; + border-color: #cf9cc3; + color: #222222; +} + +.c4:disabled { + background: #e4e4e4; + border-color: #cacaca; + color: #575757; +} + +.c1 .grey-shadow:has(+ * + .internal-button:focus-visible) { + box-shadow: 0 0 0 0.3rem rgba(87,87,87,100%); +} + +.c1 .yellow-shadow:has(+ .internal-button:focus-visible) { + box-shadow: 0 0 0 0.125rem rgba(255,229,85,100%); +} + +.c1 .yellow-shadow:has(+ .internal-button:hover), +.c1 .yellow-shadow:has(+ .internal-button:hover:not(:focus-visible,:active)) { + box-shadow: 0.125rem 0.125rem 0 rgba(255,229,85,100%); +} + +.c1 .grey-shadow:has(+ * + .internal-button:hover) { + box-shadow: none; +} + +.c1 .grey-shadow:has(+ * + .internal-button:active) { + box-shadow: 0.25rem 0.25rem 0 rgba(87,87,87,100%); +} + +.c1 .yellow-shadow:has(+ .internal-button:active) { + box-shadow: 0.125rem 0.125rem 0 rgba(255,229,85,100%); +} + +
+
+
+ +
+`; diff --git a/src/components/organisms/pupil/OakPupilJourneyYearButton/index.ts b/src/components/organisms/pupil/OakPupilJourneyYearButton/index.ts new file mode 100644 index 00000000..151c7a18 --- /dev/null +++ b/src/components/organisms/pupil/OakPupilJourneyYearButton/index.ts @@ -0,0 +1 @@ +export * from "./OakPupilJourneyYearButton"; diff --git a/src/components/organisms/pupil/index.ts b/src/components/organisms/pupil/index.ts index 411655cb..98746ac2 100644 --- a/src/components/organisms/pupil/index.ts +++ b/src/components/organisms/pupil/index.ts @@ -14,3 +14,4 @@ export * from "./OakLessonVideoTranscript"; export * from "./OakQuizOrder"; export * from "./OakQuizMatch"; export * from "./OakPupilJourneyLayout"; +export * from "./OakPupilJourneyYearButton";