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

chore(ui): migrate ThemeToggle to Typescript #666

Merged
merged 5 commits into from
Dec 6, 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
5 changes: 5 additions & 0 deletions .changeset/sharp-fishes-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-ui-components": minor
---

Migrate the ThemeToggle component to TypeScript
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react"
import PropTypes from "prop-types"
import { StyleProvider } from "../../deprecated_js/StyleProvider/index"
import { Icon } from "../../deprecated_js/Icon"

import { StyleProvider } from "../StyleProvider/StyleProvider.component"
import { Icon } from "../Icon/Icon.component"

import "./themeToggle.scss"

const toggleStyles = `
Expand All @@ -22,12 +23,43 @@ const toggleStyles = `
active:jn-bg-theme-background-lvl-4
`

/**
A Toggle button to toggle Light and Dark UI Themes.
Requires a StyleProvider context, which is automatically provided by the Juno AppShell.
If you are not using AppShell, include a StyleProvider manually.
export interface ThemeToggleProps {
/**
* Additional CSS classes for custom styling.
*/
className?: string
/**
* If true, the ThemeToggle will be disabled and not respond to user input.
*/
disabled?: boolean
/**
* HTML id attribute for the ThemeToggle.
*/
id?: string
/**
* HTML name attribute for the ThemeToggle.
*/
name?: string
/**
* Callback function that is called when the theme is toggled.
*/
// eslint-disable-next-line no-unused-vars
onToggleTheme?: (...args: unknown[]) => void
}

/**
* ThemeToggle is a button component that toggles between Light and Dark UI Themes.
* This component requires a StyleProvider context to function, which is automatically provided by the Juno AppShell.
* If not using the AppShell, include a StyleProvider manually.
*/
export const ThemeToggle = ({ className = "", disabled = false, id, name, onToggleTheme, ...props }) => {
export const ThemeToggle: React.FC<ThemeToggleProps> = ({
className = "",
disabled = false,
id,
name,
onToggleTheme,
...props
}) => {
// Consume the theme context
const ThemeContext = StyleProvider.useStyles()

Expand All @@ -39,7 +71,7 @@ export const ThemeToggle = ({ className = "", disabled = false, id, name, onTogg
}

// Destructure the context, fallback
const { currentTheme: currentTheme, setThemeClass: setThemeClass } = ThemeContext || {}
const { currentTheme, setThemeClass } = ThemeContext || {}

const toggleTheme = () => {
const newTheme = currentTheme === "theme-dark" ? "theme-light" : "theme-dark"
Expand All @@ -59,16 +91,3 @@ export const ThemeToggle = ({ className = "", disabled = false, id, name, onTogg
/>
)
}

ThemeToggle.propTypes = {
/** Pass a custom className */
className: PropTypes.string,
/** Whether the ThemeToggle is disabled */
disabled: PropTypes.bool,
/** Optional of the ThemeToggle */
id: PropTypes.string,
/** Optional name attribute of the ThemeToggle */
name: PropTypes.string,
/** Handler to execute when the theme is toggled */
onToggleTheme: PropTypes.func,
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ThemeToggle } from "./ThemeToggle.component"

export default {
title: "WIP/ThemeToggle",
component: ThemeToggle,
argTypes: {},
}

export const Default = {
parameters: {},
args: {},
}

export const Disabled = {
args: {
disabled: true,
},
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
import * as React from "react"
import React from "react"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { ThemeToggle } from "./index"
import { describe, expect, test, beforeEach, afterEach, vi } from "vitest"

const mockOnToggleTheme = jest.fn()
import { ThemeToggle } from "./ThemeToggle.component"

const mockOnToggleTheme = vi.fn()

describe("ThemeToggle", () => {
let consoleWarnSpy
let consoleWarnSpy: ReturnType<typeof vi.spyOn>

// Set up console.warn spy
beforeEach(() => {
consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {})
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
})

// Restore console.warn after each test
afterEach(() => {
consoleWarnSpy.mockRestore()
mockOnToggleTheme.mockClear()
})

test("render a ThemeToggle", async () => {
test("render a ThemeToggle", () => {
render(<ThemeToggle />)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveClass("juno-theme-toggle")
})

test("renders a disabled ThemeToggle as passed", async () => {
test("renders a disabled ThemeToggle as passed", () => {
render(<ThemeToggle disabled />)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toBeDisabled()
})

test("renders an id as passed", async () => {
test("renders an id as passed", () => {
render(<ThemeToggle id="my-theme-toggle" />)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveAttribute("id", "my-theme-toggle")
})

test("renders a name as passed", async () => {
test("renders a name as passed", () => {
render(<ThemeToggle name="my-theme-toggle" />)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveAttribute("name", "my-theme-toggle")
})

test("logs a console warning when no theme context is provided", async () => {
test("logs a console warning when no theme context is provided", () => {
render(<ThemeToggle name="my-theme-toggle" />)
expect(consoleWarnSpy).toHaveBeenCalledWith(
"Juno ThemeToggle requires a StyleProvider context in order to work. Use ThemeToggle in a Juno AppShell or include StyleProvider manually."
Expand All @@ -53,19 +56,21 @@ describe("ThemeToggle", () => {
render(<ThemeToggle onToggleTheme={mockOnToggleTheme} />)
const user = userEvent.setup()
const toggleButton = screen.getByRole("button")
user.click(toggleButton)

await user.click(toggleButton)

await waitFor(() => {
expect(mockOnToggleTheme).toHaveBeenCalled()
})
})

test("renders a custom classNames as passed", async () => {
test("renders a custom classNames as passed", () => {
render(<ThemeToggle className="my-custom-class" />)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveClass("my-custom-class")
})

test("renders arbitrary props as passed", async () => {
test("renders arbitrary props as passed", () => {
render(<ThemeToggle data-lolol="My theme toggle" />)
expect(screen.getByRole("button")).toHaveAttribute("data-lolol", "My theme toggle")
})
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ThemeToggle } from "./ThemeToggle.component"
2 changes: 1 addition & 1 deletion packages/ui-components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export { Tabs } from "./components/Tabs/index.js"
export { TextareaRow } from "./components/TextareaRow/index.js"
export { TextInput } from "./components/TextInput/index.js"
export { TextInputRow } from "./components/TextInputRow/index.js"
export { ThemeToggle } from "./components/ThemeToggle/index.js"
export { ThemeToggle } from "./components/ThemeToggle/ThemeToggle.component"
export { Toast } from "./components/Toast/index.js"
export { Tooltip } from "./components/Tooltip/index.js"
export { TooltipContent } from "./components/TooltipContent/index.js"
Expand Down
Loading