Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type {Meta} from '@storybook/react-vite'
import {Button} from '../Button'
import {ActionMenu} from '../ActionMenu'
import {ActionList} from '../ActionList'
import {ConfirmationDialog, useConfirm} from './ConfirmationDialog'
import {ConfirmationDialog} from './ConfirmationDialog'
import {useConfirm} from './useConfirm'
import classes from './ConfirmationDialog.features.stories.module.css'

export default {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {useCallback, useRef, useState} from 'react'
import {ActionMenu} from '../deprecated/ActionMenu'
import BaseStyles from '../BaseStyles'
import {Button} from '../Button'
import {ConfirmationDialog, useConfirm} from './ConfirmationDialog'
import {ConfirmationDialog} from './ConfirmationDialog'
import {useConfirm} from './useConfirm'
import {Stack} from '../Stack'
import {implementsClassName} from '../utils/testing'
import dialogClasses from '../Dialog/Dialog.module.css'
Expand Down
40 changes: 0 additions & 40 deletions packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type React from 'react'
import {useCallback} from 'react'
import {createRoot} from 'react-dom/client'
import type {DialogButtonProps, DialogWidth, DialogHeight} from '../Dialog'
import {Dialog} from '../Dialog'
import BaseStyles from '../BaseStyles'

/**
* Props to customize the ConfirmationDialog.
Expand Down Expand Up @@ -137,41 +135,3 @@ export const ConfirmationDialog: React.FC<React.PropsWithChildren<ConfirmationDi
</Dialog>
)
}

let hostElement: Element | null = null
export type ConfirmOptions = Omit<ConfirmationDialogProps, 'onClose'> & {content: React.ReactNode}
async function confirm(options: ConfirmOptions): Promise<boolean> {
const {content, ...confirmationDialogProps} = options
return new Promise(resolve => {
hostElement ||= document.createElement('div')
if (!hostElement.isConnected) document.body.append(hostElement)
const root = createRoot(hostElement)
const onClose: ConfirmationDialogProps['onClose'] = gesture => {
root.unmount()
if (gesture === 'confirm') {
resolve(true)
} else {
resolve(false)
}
}
root.render(
<BaseStyles>
<ConfirmationDialog {...confirmationDialogProps} onClose={onClose}>
{content}
</ConfirmationDialog>
</BaseStyles>,
)
})
}

/**
* This hook takes no parameters and returns an `async` function, `confirm`. When `confirm`
* is called, it shows the confirmation dialog. When the dialog is dismissed, a promise is
* resolved with `true` or `false` depending on whether or not the confirm button was used.
*/
export function useConfirm() {
const result = useCallback((options: ConfirmOptions) => {
return confirm(options)
}, [])
return result
}
44 changes: 44 additions & 0 deletions packages/react/src/ConfirmationDialog/useConfirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, {useCallback} from 'react'
import {createRoot} from 'react-dom/client'
import BaseStyles from '../BaseStyles'
import {ConfirmationDialog, type ConfirmationDialogProps} from './ConfirmationDialog'

let hostElement: Element | null = null

export type ConfirmOptions = Omit<ConfirmationDialogProps, 'onClose'> & {content: React.ReactNode}

async function confirm(options: ConfirmOptions): Promise<boolean> {
const {content, ...confirmationDialogProps} = options
return new Promise(resolve => {
hostElement ||= document.createElement('div')
if (!hostElement.isConnected) document.body.append(hostElement)
const root = createRoot(hostElement)
const onClose: ConfirmationDialogProps['onClose'] = gesture => {
root.unmount()
if (gesture === 'confirm') {
resolve(true)
} else {
resolve(false)
}
}
root.render(
React.createElement(
BaseStyles,
null,
React.createElement(ConfirmationDialog, {...confirmationDialogProps, onClose}, content),
),
)
})
}

/**
* This hook takes no parameters and returns an `async` function, `confirm`. When `confirm`
* is called, it shows the confirmation dialog. When the dialog is dismissed, a promise is
* resolved with `true` or `false` depending on whether or not the confirm button was used.
*/
export function useConfirm() {
const result = useCallback((options: ConfirmOptions) => {
return confirm(options)
}, [])
return result
}
19 changes: 19 additions & 0 deletions packages/react/src/ThemeContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import type {ColorMode, ColorModeWithAuto, Theme} from './ThemeProvider'

export const ThemeContext = React.createContext<{
theme?: Theme
colorScheme?: string
colorMode?: ColorModeWithAuto
resolvedColorMode?: ColorMode
resolvedColorScheme?: string
dayScheme?: string
nightScheme?: string
setColorMode: React.Dispatch<React.SetStateAction<ColorModeWithAuto>>
setDayScheme: React.Dispatch<React.SetStateAction<string>>
setNightScheme: React.Dispatch<React.SetStateAction<string>>
}>({
setColorMode: () => null,
setDayScheme: () => null,
setNightScheme: () => null,
})
30 changes: 3 additions & 27 deletions packages/react/src/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import defaultTheme from './theme'
import deepmerge from 'deepmerge'
import {useId} from './hooks'
import {useSyncedState} from './hooks/useSyncedState'
import {ThemeContext} from './ThemeContext'
import {useTheme} from './useTheme'

export const defaultColorMode = 'day'
const defaultDayScheme = 'light'
const defaultNightScheme = 'dark'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Theme = {[key: string]: any}
type ColorMode = 'day' | 'night' | 'light' | 'dark'
export type ColorMode = 'day' | 'night' | 'light' | 'dark'
export type ColorModeWithAuto = ColorMode | 'auto'

export type ThemeProviderProps = {
Expand All @@ -26,23 +28,6 @@ export type ThemeProviderProps = {
contextOnly?: boolean
}

const ThemeContext = React.createContext<{
theme?: Theme
colorScheme?: string
colorMode?: ColorModeWithAuto
resolvedColorMode?: ColorMode
resolvedColorScheme?: string
dayScheme?: string
nightScheme?: string
setColorMode: React.Dispatch<React.SetStateAction<ColorModeWithAuto>>
setDayScheme: React.Dispatch<React.SetStateAction<string>>
setNightScheme: React.Dispatch<React.SetStateAction<string>>
}>({
setColorMode: () => null,
setDayScheme: () => null,
setNightScheme: () => null,
})

// inspired from __NEXT_DATA__, we use application/json to avoid CSRF policy with inline scripts
const serverHandoffCache = new Map<string, Record<string, unknown>>()
const emptyHandoff: Record<string, unknown> = {}
Expand Down Expand Up @@ -160,15 +145,6 @@ export const ThemeProvider: React.FC<React.PropsWithChildren<ThemeProviderProps>
)
}

export function useTheme() {
return React.useContext(ThemeContext)
}

export function useColorSchemeVar(values: Partial<Record<string, string>>, fallback: string) {
const {colorScheme = ''} = useTheme()
return values[colorScheme] ?? fallback
}

function subscribeToSystemColorMode(callback: () => void) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const media = window?.matchMedia?.('(prefers-color-scheme: dark)')
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/__tests__/ThemeProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {render, screen, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {describe, expect, it, vi} from 'vitest'
import React from 'react'
import {ThemeProvider, useColorSchemeVar, useTheme} from '../ThemeProvider'
import ThemeProvider from '../ThemeProvider'
import {useColorSchemeVar, useTheme} from '../useTheme'

// copied from '@primer/primitives/dist/css/functional/themes/';
const fgDefaultColors = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {Meta} from '@storybook/react-vite'
import {action} from 'storybook/actions'
import React from 'react'
import {Tabs, TabPanel, useTabList, useTab} from './Tabs'
import {Tabs, TabPanel} from './Tabs'
import {useTab} from './useTab'
import {useTabList} from './useTabList'
import {ActionList} from '../../ActionList'
import Flash from '../../Flash'

Expand Down
Loading
Loading