Skip to content

Commit 67e78ec

Browse files
committed
core.ignorecase: true -> false
1 parent 22c2053 commit 67e78ec

File tree

6 files changed

+508
-0
lines changed

6 files changed

+508
-0
lines changed

src/components/Layout.tsx

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Writable } from 'utility-types'
2+
3+
import { useEffect } from 'react'
4+
import { NextUIProvider, twMergeConfig, cn } from '@nextui-org/react'
5+
6+
import { useTheme } from '@/hooks/useTheme'
7+
8+
import '@/assets/style.css'
9+
10+
const classGroups = twMergeConfig.classGroups as Record<
11+
string,
12+
Writable<NonNullable<(typeof twMergeConfig)['classGroups']>[string]>
13+
>
14+
15+
classGroups!['font-size'].push({ text: ['mini'] })
16+
17+
export type LayoutProps = {
18+
children: React.ReactNode
19+
className?: string
20+
style?: React.CSSProperties
21+
}
22+
23+
export const Layout: React.FC<LayoutProps> = (props) => {
24+
const theme = useTheme()
25+
26+
useEffect(() => {
27+
document.body.className = cn(
28+
theme || 'hidden',
29+
'bg-background text-foreground'
30+
)
31+
}, [theme])
32+
33+
return (
34+
<NextUIProvider locale="ja-JP">
35+
<main
36+
className={cn('overflow-y-auto overflow-x-hidden', props.className)}
37+
style={props.style}
38+
>
39+
{props.children}
40+
</main>
41+
</NextUIProvider>
42+
)
43+
}

src/components/Modal.tsx

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type { ModalProps as NextUIModalProps } from '@nextui-org/react'
2+
3+
import { useCallback } from 'react'
4+
import {
5+
Button,
6+
Modal as NextUIModal,
7+
ModalContent as NextUIModalContent,
8+
ModalHeader as NextUIModalHeader,
9+
ModalBody as NextUIModalBody,
10+
ModalFooter as NextUIModalFooter,
11+
} from '@nextui-org/react'
12+
import { XIcon } from 'lucide-react'
13+
14+
export type ModalProps = {
15+
fullWidth?: boolean
16+
17+
isOpen: NextUIModalProps['isOpen']
18+
onOpenChange: NextUIModalProps['onOpenChange']
19+
onClose?: NextUIModalProps['onClose']
20+
21+
okText?: string
22+
okIcon?: React.ReactNode
23+
isOkDisabled?: boolean
24+
onOk?: () => void
25+
26+
cancelText?: string
27+
cancelIcon?: React.ReactNode
28+
29+
header?: React.ReactNode
30+
headerEndContent?: React.ReactNode
31+
32+
footer?: React.ReactNode
33+
34+
children: React.ReactNode
35+
}
36+
37+
export const Modal: React.FC<ModalProps> = (props) => {
38+
return (
39+
<NextUIModal
40+
classNames={{
41+
wrapper: 'justify-end',
42+
base: !props.fullWidth && 'max-w-[370px]',
43+
header: 'border-b-1 border-foreground-200 p-2 text-medium',
44+
body: 'p-0',
45+
footer: 'border-t-1 border-foreground-200 p-2',
46+
}}
47+
size="full"
48+
hideCloseButton
49+
isKeyboardDismissDisabled={true}
50+
isOpen={props.isOpen}
51+
onOpenChange={props.onOpenChange}
52+
onClose={props.onClose}
53+
>
54+
<NextUIModalContent>
55+
{(onClose) => {
56+
const onPressOk = useCallback(() => {
57+
props.onOk?.()
58+
onClose()
59+
}, [props.onOk])
60+
61+
return (
62+
<>
63+
{props.header && (
64+
<NextUIModalHeader className="flex flex-row justify-between">
65+
<div className="flex min-h-8 flex-row items-center">
66+
{props.header}
67+
</div>
68+
69+
<div className="shrink-0 font-normal">
70+
{props.headerEndContent}
71+
</div>
72+
</NextUIModalHeader>
73+
)}
74+
75+
<NextUIModalBody className="max-h-full gap-0 overflow-auto bg-background">
76+
{props.children}
77+
</NextUIModalBody>
78+
79+
{props.footer !== false && (
80+
<NextUIModalFooter>
81+
{props.footer ?? (
82+
<>
83+
<Button
84+
size="sm"
85+
variant="flat"
86+
color="default"
87+
startContent={
88+
props.cancelIcon || <XIcon className="size-4" />
89+
}
90+
onPress={onClose}
91+
>
92+
{props.cancelText || 'キャンセル'}
93+
</Button>
94+
95+
{props.onOk && (
96+
<Button
97+
size="sm"
98+
color="primary"
99+
isDisabled={props.isOkDisabled}
100+
startContent={props.okIcon}
101+
onPress={onPressOk}
102+
>
103+
{props.okText || 'OK'}
104+
</Button>
105+
)}
106+
</>
107+
)}
108+
</NextUIModalFooter>
109+
)}
110+
</>
111+
)
112+
}}
113+
</NextUIModalContent>
114+
</NextUIModal>
115+
)
116+
}

src/components/Popconfirm.tsx

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import type { PopoverProps, ButtonProps } from '@nextui-org/react'
2+
3+
import { useState, useCallback } from 'react'
4+
import {
5+
Popover,
6+
PopoverTrigger,
7+
PopoverContent,
8+
Button,
9+
cn,
10+
tv,
11+
} from '@nextui-org/react'
12+
import { InfoIcon } from 'lucide-react'
13+
14+
export type PopconfirmProps = {
15+
children: React.ReactElement
16+
17+
placement?: PopoverProps['placement']
18+
icon?: React.ReactNode
19+
title: React.ReactNode
20+
description: React.ReactNode
21+
22+
okColor?: ButtonProps['color']
23+
okText?: React.ReactNode
24+
onOk: () => void | Promise<void>
25+
26+
cancelColor?: ButtonProps['color']
27+
cancelText?: React.ReactNode
28+
onCancel?: () => void | Promise<void>
29+
}
30+
31+
const popoverIcon = tv({
32+
defaultVariants: {
33+
color: 'default',
34+
},
35+
variants: {
36+
color: {
37+
default: 'fill-blue-600/10 text-blue-600',
38+
primary: 'fill-primary/10 text-primary',
39+
secondary: 'fill-secondary/10 text-secondary',
40+
success: 'fill-success/10 text-success',
41+
warning: 'fill-warning/10 text-warning',
42+
danger: 'fill-danger/10 text-danger',
43+
},
44+
},
45+
})
46+
47+
export const Popconfirm: React.FC<PopconfirmProps> = (props) => {
48+
const [isOpen, setIsOpen] = useState(false)
49+
const [isOkLoading, setIsOkLoading] = useState(false)
50+
const [isCancelLoading, setIsCancelLoading] = useState(false)
51+
52+
const onPressCancel = useCallback(async () => {
53+
if (props.onCancel) {
54+
const response = props.onCancel()
55+
56+
if (response instanceof Promise) {
57+
setIsCancelLoading(true)
58+
await response
59+
setIsCancelLoading(false)
60+
}
61+
}
62+
63+
setIsOpen(false)
64+
}, [props.onCancel])
65+
66+
const onPressOk = useCallback(async () => {
67+
const response = props.onOk()
68+
69+
if (response instanceof Promise) {
70+
setIsOkLoading(true)
71+
await response
72+
setIsOkLoading(false)
73+
}
74+
75+
setIsOpen(false)
76+
}, [props.onOk])
77+
78+
return (
79+
<Popover
80+
classNames={{
81+
content: [
82+
'flex flex-row items-start gap-1.5 p-2.5',
83+
'border-1 border-foreground-100',
84+
],
85+
}}
86+
placement={props.placement}
87+
backdrop="opaque"
88+
showArrow
89+
isOpen={isOpen}
90+
onOpenChange={setIsOpen}
91+
>
92+
<PopoverTrigger>{props.children}</PopoverTrigger>
93+
94+
<PopoverContent>
95+
<div className="flex h-5 shrink-0 items-center">
96+
{props.icon || (
97+
<InfoIcon
98+
className={popoverIcon({ color: props.okColor })}
99+
size={16}
100+
/>
101+
)}
102+
</div>
103+
104+
<div className="flex flex-col gap-1.5">
105+
<span className="font-semibold">{props.title}</span>
106+
107+
<span
108+
className={cn(
109+
'text-tiny',
110+
'text-foreground-500 dark:text-foreground-600'
111+
)}
112+
>
113+
{props.description}
114+
</span>
115+
116+
<div className="ml-auto mt-1.5 flex flex-row gap-2">
117+
<Button
118+
className="h-7"
119+
size="sm"
120+
color={props.cancelColor || 'default'}
121+
variant="flat"
122+
isLoading={isCancelLoading}
123+
onPress={onPressCancel}
124+
>
125+
{props.cancelText || 'キャンセル'}
126+
</Button>
127+
128+
<Button
129+
className="h-7"
130+
size="sm"
131+
color={props.okColor || 'primary'}
132+
variant="solid"
133+
isLoading={isOkLoading}
134+
onPress={onPressOk}
135+
>
136+
{props.okText || 'OK'}
137+
</Button>
138+
</div>
139+
</div>
140+
</PopoverContent>
141+
</Popover>
142+
)
143+
}

src/components/Segmented.tsx

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { TabsProps } from '@nextui-org/react'
2+
3+
import { Tabs, Tab, cn, tv } from '@nextui-org/react'
4+
5+
export type SegmentedProps = {
6+
className?: TabsProps['className']
7+
8+
items: SegmentedItem[]
9+
size?: TabsProps['size']
10+
radius?: TabsProps['radius']
11+
fullWidth?: TabsProps['fullWidth']
12+
label?: string
13+
selectedKey?: string
14+
defaultSelectedKey?: string
15+
onSelectionChange?: (key: string) => void
16+
}
17+
18+
export type SegmentedItem = {
19+
key: string | number
20+
children: React.ReactNode
21+
startContent?: React.ReactNode
22+
}
23+
24+
const segmentedLabel = tv({
25+
base: 'my-1',
26+
variants: {
27+
size: {
28+
sm: 'text-small',
29+
md: 'text-medium',
30+
lg: 'text-large',
31+
},
32+
},
33+
})
34+
35+
export const Segmented: React.FC<SegmentedProps> = (props) => {
36+
return (
37+
<div className={cn('flex flex-col gap-1', props.className)}>
38+
{props.label && (
39+
<span className={segmentedLabel({ size: props.size })}>
40+
{props.label}
41+
</span>
42+
)}
43+
44+
<Tabs
45+
color="primary"
46+
size={props.size}
47+
radius={props.radius}
48+
fullWidth={props.fullWidth}
49+
selectedKey={props.selectedKey}
50+
defaultSelectedKey={props.defaultSelectedKey}
51+
onSelectionChange={
52+
props.onSelectionChange as TabsProps['onSelectionChange']
53+
}
54+
>
55+
{props.items.map(({ key, children, startContent }) => (
56+
<Tab
57+
key={key}
58+
title={
59+
startContent ? (
60+
<div className="flex flex-row items-center gap-1">
61+
{startContent}
62+
<span>{children}</span>
63+
</div>
64+
) : (
65+
children
66+
)
67+
}
68+
/>
69+
))}
70+
</Tabs>
71+
</div>
72+
)
73+
}

0 commit comments

Comments
 (0)