Skip to content

Commit

Permalink
chore(ui): migrate ThemeToggle to Typescript (#666)
Browse files Browse the repository at this point in the history
* chore(ui): initially convert themetoggle to ts

* chore(ui): fix tests and improve component

* chore(ui): fix tests and improve component

* chore(ui): update src index export

* chore(ui): remove commented code
  • Loading branch information
guoda-puidokaite authored Dec 6, 2024
1 parent e96f612 commit a3c43a0
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 78 deletions.
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
1 change: 0 additions & 1 deletion packages/ui-components/src/components/ThemeToggle/index.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/ui-components/src/components/ThemeToggle/index.ts
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

0 comments on commit a3c43a0

Please sign in to comment.