@charcoal-ui/react 6.1.0-beta.0 → 6.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/components/Notification/NotificationItem.cjs +30 -0
  2. package/dist/components/Notification/NotificationItem.cjs.map +1 -0
  3. package/dist/components/Notification/NotificationItem.d.ts +15 -0
  4. package/dist/components/Notification/NotificationItem.d.ts.map +1 -0
  5. package/dist/components/Notification/NotificationItem.js +30 -0
  6. package/dist/components/Notification/NotificationItem.js.map +1 -0
  7. package/dist/components/Notification/NotificationRegion.cjs +50 -0
  8. package/dist/components/Notification/NotificationRegion.cjs.map +1 -0
  9. package/dist/components/Notification/NotificationRegion.d.ts +14 -0
  10. package/dist/components/Notification/NotificationRegion.d.ts.map +1 -0
  11. package/dist/components/Notification/NotificationRegion.js +50 -0
  12. package/dist/components/Notification/NotificationRegion.js.map +1 -0
  13. package/dist/components/Notification/Snackbar/index.d.ts +45 -0
  14. package/dist/components/Notification/Snackbar/index.d.ts.map +1 -0
  15. package/dist/components/Notification/Snackbar/index2.cjs +99 -0
  16. package/dist/components/Notification/Snackbar/index2.cjs.map +1 -0
  17. package/dist/components/Notification/Snackbar/index2.js +94 -0
  18. package/dist/components/Notification/Snackbar/index2.js.map +1 -0
  19. package/dist/components/Notification/Toast/index.d.ts +28 -0
  20. package/dist/components/Notification/Toast/index.d.ts.map +1 -0
  21. package/dist/components/Notification/Toast/index2.cjs +86 -0
  22. package/dist/components/Notification/Toast/index2.cjs.map +1 -0
  23. package/dist/components/Notification/Toast/index2.js +81 -0
  24. package/dist/components/Notification/Toast/index2.js.map +1 -0
  25. package/dist/components/Notification/types.d.ts +27 -0
  26. package/dist/components/Notification/types.d.ts.map +1 -0
  27. package/dist/components/Notification/useNotificationQueue.cjs +155 -0
  28. package/dist/components/Notification/useNotificationQueue.cjs.map +1 -0
  29. package/dist/components/Notification/useNotificationQueue.d.ts +16 -0
  30. package/dist/components/Notification/useNotificationQueue.d.ts.map +1 -0
  31. package/dist/components/Notification/useNotificationQueue.js +155 -0
  32. package/dist/components/Notification/useNotificationQueue.js.map +1 -0
  33. package/dist/index.cjs +4 -1
  34. package/dist/index.css +1315 -1250
  35. package/dist/index.d.ts +3 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/layered.css +1315 -1250
  39. package/dist/layered.css.map +1 -1
  40. package/package.json +5 -5
  41. package/src/__tests__/css-output.test.ts +39 -6
  42. package/src/components/Notification/NotificationItem.tsx +51 -0
  43. package/src/components/Notification/NotificationRegion.tsx +76 -0
  44. package/src/components/{Snackbar → Notification/Snackbar}/__snapshots__/index.css.snap +1 -42
  45. package/src/components/{Snackbar → Notification/Snackbar}/index.css +1 -43
  46. package/src/components/{Snackbar → Notification/Snackbar}/index.story.tsx +52 -23
  47. package/src/components/{Snackbar → Notification/Snackbar}/index.test.tsx +61 -11
  48. package/src/components/Notification/Snackbar/index.tsx +131 -0
  49. package/src/components/Notification/Toast/__snapshots__/index.css.snap +62 -0
  50. package/src/components/Notification/Toast/index.css +61 -0
  51. package/src/components/Notification/Toast/index.story.tsx +174 -0
  52. package/src/components/Notification/Toast/index.test.tsx +263 -0
  53. package/src/components/Notification/Toast/index.tsx +86 -0
  54. package/src/components/Notification/__snapshots__/layout.css.snap +47 -0
  55. package/src/components/Notification/layout.css +46 -0
  56. package/src/components/Notification/types.ts +28 -0
  57. package/src/components/Notification/useNotificationQueue.ts +235 -0
  58. package/src/index.ts +14 -2
  59. package/dist/components/Snackbar/index.d.ts +0 -52
  60. package/dist/components/Snackbar/index.d.ts.map +0 -1
  61. package/dist/components/Snackbar/index2.cjs +0 -268
  62. package/dist/components/Snackbar/index2.cjs.map +0 -1
  63. package/dist/components/Snackbar/index2.js +0 -263
  64. package/dist/components/Snackbar/index2.js.map +0 -1
  65. package/src/components/Snackbar/index.tsx +0 -455
@@ -0,0 +1,174 @@
1
+ import { useEffect, type ReactNode } from 'react'
2
+ import { Meta, StoryObj } from '@storybook/react-vite'
3
+ import Button from '../../Button'
4
+ import unstable_Toast, {
5
+ useToast as unstable_useToast,
6
+ type ToastProps as unstable_ToastProps,
7
+ type ShowToastOptions as unstable_ShowToastOptions,
8
+ } from '.'
9
+
10
+ const defaultOpen = !!process.env.TEST
11
+
12
+ type ToastStoryArgs = unstable_ToastProps & {
13
+ message: ReactNode
14
+ type: unstable_ShowToastOptions['type']
15
+ }
16
+
17
+ export default {
18
+ title: 'react/unstable_Toast',
19
+ component: unstable_Toast,
20
+ parameters: {
21
+ layout: 'centered',
22
+ tokenVersion: 'v2',
23
+ controls: {
24
+ sort: 'requiredFirst',
25
+ },
26
+ docs: {
27
+ description: {
28
+ component: `同時に表示できるトーストは1つのみです。表示中に別のトーストが発生した場合は、\`order\` に応じてキューに積むか、表示中のトーストを置き換えます。
29
+
30
+ 別々の \`useToast\` を呼び出した場合は互いに独立するため、同時表示数は1件に制限されません。`,
31
+ },
32
+ },
33
+ },
34
+ args: {
35
+ message: '保存しました',
36
+ duration: 5000,
37
+ order: 'queue',
38
+ type: 'success',
39
+ },
40
+ argTypes: {
41
+ position: {
42
+ options: ['top', 'bottom'],
43
+ control: { type: 'inline-radio' },
44
+ table: { category: 'Hook' },
45
+ },
46
+ offset: {
47
+ control: { type: 'number', min: 0, step: 1 },
48
+ table: { category: 'Hook' },
49
+ },
50
+ duration: {
51
+ control: { type: 'number', min: 0, step: 500 },
52
+ description: '表示時間(ミリ秒)',
53
+ table: { category: 'Hook' },
54
+ },
55
+ order: {
56
+ options: ['queue', 'replace'],
57
+ control: { type: 'inline-radio' },
58
+ table: { category: 'Hook' },
59
+ },
60
+ zIndex: {
61
+ control: { type: 'number', min: 0, step: 1 },
62
+ table: { category: 'Hook' },
63
+ },
64
+ className: {
65
+ control: 'text',
66
+ table: { category: 'Hook' },
67
+ },
68
+ portalContainer: {
69
+ control: false,
70
+ table: { category: 'Hook' },
71
+ },
72
+ css: {
73
+ table: { disable: true },
74
+ },
75
+ type: {
76
+ options: ['success', 'error'],
77
+ control: { type: 'inline-radio' },
78
+ table: { category: 'Show' },
79
+ },
80
+ message: {
81
+ control: 'text',
82
+ table: { category: 'Show' },
83
+ },
84
+ },
85
+ render: (args) => <ToastDemo {...args} />,
86
+ } as Meta<ToastStoryArgs>
87
+
88
+ function ToastDemo({ message, type, ...props }: ToastStoryArgs) {
89
+ const [toast, showToast] = unstable_useToast(props)
90
+ const showOptions = {
91
+ type,
92
+ }
93
+
94
+ useEffect(() => {
95
+ if (!defaultOpen) {
96
+ return
97
+ }
98
+ showToast(message, {
99
+ type,
100
+ })
101
+ }, [message, showToast, type])
102
+
103
+ return (
104
+ <>
105
+ {toast}
106
+ <Button
107
+ onClick={() => {
108
+ showToast(message, showOptions)
109
+ }}
110
+ >
111
+ show
112
+ </Button>
113
+ </>
114
+ )
115
+ }
116
+
117
+ export const Default: StoryObj<ToastStoryArgs> = {
118
+ parameters: {
119
+ docs: {
120
+ source: {
121
+ language: 'tsx',
122
+ code: `import { Button, unstable_useToast } from '@charcoal-ui/react'
123
+
124
+ export function Example() {
125
+ const [toast, showToast] = unstable_useToast()
126
+
127
+ return (
128
+ <>
129
+ {toast}
130
+ <Button
131
+ onClick={() => showToast('保存しました', { type: 'success' })}
132
+ >
133
+ 保存
134
+ </Button>
135
+ </>
136
+ )
137
+ }`,
138
+ },
139
+ },
140
+ },
141
+ }
142
+
143
+ export const Error: StoryObj<ToastStoryArgs> = {
144
+ args: {
145
+ type: 'error',
146
+ message: '保存に失敗しました',
147
+ },
148
+ }
149
+
150
+ export const LongMessage: StoryObj<ToastStoryArgs> = {
151
+ args: {
152
+ message:
153
+ '保存した内容はすべての端末に同期され、あとから設定画面で変更できます。長いメッセージは2行を超えると省略されます',
154
+ },
155
+ }
156
+
157
+ export const WithLineBreak: StoryObj<ToastStoryArgs> = {
158
+ args: {
159
+ message: (
160
+ <>
161
+ 保存に失敗しました
162
+ <br />
163
+ 通信環境を確認してください
164
+ </>
165
+ ),
166
+ },
167
+ }
168
+
169
+ export const Bottom: StoryObj<ToastStoryArgs> = {
170
+ args: {
171
+ position: 'bottom',
172
+ message: '下部に表示します',
173
+ },
174
+ }
@@ -0,0 +1,263 @@
1
+ import { createRef, type ComponentProps } from 'react'
2
+ import { render, fireEvent, act, cleanup, screen } from '@testing-library/react'
3
+ import { vi, describe, it, expect, afterEach, beforeEach } from 'vitest'
4
+ import Toast, { useToast, type ToastHandler } from '.'
5
+
6
+ function renderToast(props: ComponentProps<typeof Toast> = {}) {
7
+ const ref = createRef<ToastHandler>()
8
+ const result = render(<Toast ref={ref} {...props} />)
9
+ const show: ToastHandler['show'] = (message, options) => {
10
+ act(() => {
11
+ ref.current?.show(message, options)
12
+ })
13
+ act(() => {
14
+ vi.advanceTimersByTime(300)
15
+ })
16
+ }
17
+ return { ...result, show }
18
+ }
19
+
20
+ function finishExitAnimation(message: string) {
21
+ const toast = screen.getByText(message).closest('.charcoal-toast')
22
+ if (toast === null) throw new Error('Toast not found')
23
+ fireEvent(
24
+ toast,
25
+ Object.assign(new Event('animationend', { bubbles: true }), {
26
+ animationName: 'charcoal-toast-exit',
27
+ }),
28
+ )
29
+ }
30
+
31
+ describe('Toast', () => {
32
+ beforeEach(() => {
33
+ vi.useFakeTimers()
34
+ })
35
+
36
+ afterEach(() => {
37
+ cleanup()
38
+ vi.useRealTimers()
39
+ })
40
+
41
+ it('shows a message and hides after 5 seconds', () => {
42
+ const { show } = renderToast()
43
+
44
+ expect(screen.queryByRole('region')).not.toBeInTheDocument()
45
+
46
+ show('保存しました', { type: 'success' })
47
+
48
+ expect(screen.getByText('保存しました')).toBeInTheDocument()
49
+
50
+ act(() => {
51
+ vi.advanceTimersByTime(5000)
52
+ })
53
+
54
+ expect(screen.getByText('保存しました')).toBeInTheDocument()
55
+ expect(
56
+ screen.getByText('保存しました').closest('.charcoal-toast'),
57
+ ).toHaveAttribute('data-exiting', 'true')
58
+
59
+ finishExitAnimation('保存しました')
60
+
61
+ expect(screen.queryByText('保存しました')).not.toBeInTheDocument()
62
+ })
63
+
64
+ it.each([0, -1])('clamps duration to zero: %s', (duration) => {
65
+ const { show } = renderToast({ duration })
66
+
67
+ show('保存しました', { type: 'success' })
68
+ expect(screen.getByText('保存しました')).toBeInTheDocument()
69
+
70
+ act(() => {
71
+ vi.advanceTimersByTime(1)
72
+ })
73
+ expect(
74
+ screen.getByText('保存しました').closest('.charcoal-toast'),
75
+ ).toHaveAttribute('data-exiting', 'true')
76
+
77
+ finishExitAnimation('保存しました')
78
+ expect(screen.queryByText('保存しました')).not.toBeInTheDocument()
79
+ })
80
+
81
+ it.each([
82
+ Number.NaN,
83
+ Number.POSITIVE_INFINITY,
84
+ Number.NEGATIVE_INFINITY,
85
+ 'invalid' as unknown as number,
86
+ ])('falls back to the default duration: %s', (duration) => {
87
+ const { show } = renderToast({ duration })
88
+
89
+ show('保存しました', { type: 'success' })
90
+ act(() => {
91
+ vi.advanceTimersByTime(4999)
92
+ })
93
+ expect(screen.getByText('保存しました')).toBeInTheDocument()
94
+
95
+ act(() => {
96
+ vi.advanceTimersByTime(1)
97
+ })
98
+ expect(
99
+ screen.getByText('保存しました').closest('.charcoal-toast'),
100
+ ).toHaveAttribute('data-exiting', 'true')
101
+ })
102
+
103
+ it('queues the next toast until the previous one closes', async () => {
104
+ const { show } = renderToast()
105
+
106
+ show('first', { type: 'success' })
107
+ show('second', { type: 'error' })
108
+
109
+ expect(screen.getByText('first')).toBeInTheDocument()
110
+ expect(screen.queryByText('second')).not.toBeInTheDocument()
111
+
112
+ act(() => {
113
+ vi.advanceTimersByTime(5000)
114
+ })
115
+ await act(async () => {
116
+ finishExitAnimation('first')
117
+ })
118
+
119
+ expect(screen.queryByText('first')).not.toBeInTheDocument()
120
+ expect(screen.getByText('second')).toBeInTheDocument()
121
+ expect(
122
+ screen.getByText('second').closest('.charcoal-toast'),
123
+ ).toHaveAttribute('data-type', 'error')
124
+ })
125
+
126
+ it('replaces the current toast immediately when requested', () => {
127
+ const { show } = renderToast({ order: 'replace' })
128
+
129
+ show('first', { type: 'success' })
130
+ show('second', { type: 'error' })
131
+
132
+ expect(screen.queryByText('first')).not.toBeInTheDocument()
133
+ expect(screen.getByText('second')).toBeInTheDocument()
134
+ })
135
+
136
+ it('keeps the toast open while hovered after the timer ends, then closes on leave', () => {
137
+ const { show } = renderToast()
138
+
139
+ show('保存しました', { type: 'success' })
140
+ const toast = screen.getByText('保存しました').closest('.charcoal-toast')
141
+ if (toast === null) throw new Error('Toast not found')
142
+
143
+ fireEvent.pointerEnter(toast, { pointerType: 'mouse' })
144
+ act(() => {
145
+ vi.advanceTimersByTime(5000)
146
+ })
147
+ expect(toast).not.toHaveAttribute('data-exiting', 'true')
148
+
149
+ fireEvent.pointerLeave(toast)
150
+ expect(toast).toHaveAttribute('data-exiting', 'true')
151
+ })
152
+
153
+ it('keeps the toast open while focused', () => {
154
+ const { show } = renderToast()
155
+
156
+ show('保存しました', { type: 'success' })
157
+ const toast = screen.getByText('保存しました').closest('.charcoal-toast')
158
+ if (toast === null) throw new Error('Toast not found')
159
+
160
+ fireEvent.keyDown(document, { key: 'F6' })
161
+ expect(screen.getByRole('region')).toHaveFocus()
162
+
163
+ act(() => {
164
+ vi.advanceTimersByTime(5000)
165
+ })
166
+ expect(toast).not.toHaveAttribute('data-exiting', 'true')
167
+
168
+ fireEvent.blur(screen.getByRole('region'))
169
+ act(() => {
170
+ vi.advanceTimersByTime(5000)
171
+ })
172
+ expect(toast).toHaveAttribute('data-exiting', 'true')
173
+ })
174
+
175
+ it('places a toast at the top by default', () => {
176
+ const { show } = renderToast()
177
+
178
+ show('保存しました', { type: 'success' })
179
+
180
+ expect(screen.getByRole('region')).toHaveAttribute('data-position', 'top')
181
+ })
182
+
183
+ it('places a toast at the bottom when requested', () => {
184
+ const { show } = renderToast({ position: 'bottom' })
185
+
186
+ show('保存しました', { type: 'success' })
187
+
188
+ expect(screen.getByRole('region')).toHaveAttribute(
189
+ 'data-position',
190
+ 'bottom',
191
+ )
192
+ })
193
+
194
+ it.each(['success', 'error'] as const)('applies the %s type', (type) => {
195
+ const { show } = renderToast()
196
+
197
+ show('保存しました', { type })
198
+
199
+ expect(
200
+ screen.getByText('保存しました').closest('.charcoal-toast'),
201
+ ).toHaveAttribute('data-type', type)
202
+ })
203
+
204
+ it('supports a custom z-index and portal container', () => {
205
+ const portalContainer = document.createElement('div')
206
+ document.body.append(portalContainer)
207
+ const { show } = renderToast({ zIndex: 30, portalContainer })
208
+
209
+ show('保存しました', { type: 'success' })
210
+
211
+ expect(portalContainer).toContainElement(screen.getByRole('region'))
212
+ expect(screen.getByRole('region')).toHaveStyle({ zIndex: 30 })
213
+
214
+ portalContainer.remove()
215
+ })
216
+
217
+ it('moves focus to the toast region with F6', () => {
218
+ const { show } = renderToast()
219
+
220
+ show('保存しました', { type: 'success' })
221
+
222
+ fireEvent.keyDown(document, { key: 'F6' })
223
+
224
+ expect(screen.getByRole('region')).toHaveFocus()
225
+ })
226
+ })
227
+
228
+ describe('useToast', () => {
229
+ beforeEach(() => {
230
+ vi.useFakeTimers()
231
+ })
232
+
233
+ afterEach(() => {
234
+ cleanup()
235
+ vi.useRealTimers()
236
+ })
237
+
238
+ it('shows a toast via the returned void function', () => {
239
+ let showResult: void | undefined
240
+
241
+ function HookApp() {
242
+ const [toast, show] = useToast()
243
+ return (
244
+ <>
245
+ {toast}
246
+ <button
247
+ type="button"
248
+ onClick={() => {
249
+ showResult = show('hook message', { type: 'success' })
250
+ }}
251
+ >
252
+ open
253
+ </button>
254
+ </>
255
+ )
256
+ }
257
+
258
+ render(<HookApp />)
259
+ fireEvent.click(screen.getByText('open'))
260
+ expect(screen.getByText('hook message')).toBeInTheDocument()
261
+ expect(showResult).toBeUndefined()
262
+ })
263
+ })
@@ -0,0 +1,86 @@
1
+ import '../layout.css'
2
+ import './index.css'
3
+
4
+ import { forwardRef, useImperativeHandle, useRef } from 'react'
5
+ import type { ReactNode } from 'react'
6
+ import { NotificationItem } from '../NotificationItem'
7
+ import { NotificationRegion } from '../NotificationRegion'
8
+ import { useNotificationQueue } from '../useNotificationQueue'
9
+ import type { NotificationProps, Position } from '../types'
10
+
11
+ export type ToastType = 'success' | 'error'
12
+
13
+ export type ShowToastOptions = {
14
+ type: ToastType
15
+ }
16
+
17
+ export type ToastHandler = {
18
+ show: (message: ReactNode, options: ShowToastOptions) => void
19
+ }
20
+
21
+ export type ToastProps = Omit<NotificationProps, 'position'> & {
22
+ /**
23
+ * Toast の表示位置
24
+ * @default 'top'
25
+ */
26
+ position?: Position
27
+ }
28
+
29
+ type ToastContent = {
30
+ message: ReactNode
31
+ type: ToastType
32
+ }
33
+
34
+ const Toast = forwardRef<ToastHandler, ToastProps>(function Toast(
35
+ { position = 'top', duration, order, ...regionProps },
36
+ ref,
37
+ ) {
38
+ 'use memo'
39
+
40
+ const { state, itemRef, enqueue, onHoverStart, onHoverEnd } =
41
+ useNotificationQueue<ToastContent>('toast', { duration, order })
42
+
43
+ function show(message: ReactNode, options: ShowToastOptions) {
44
+ enqueue({
45
+ message,
46
+ type: options.type,
47
+ })
48
+ }
49
+
50
+ useImperativeHandle(ref, () => ({ show }))
51
+
52
+ return (
53
+ <NotificationRegion
54
+ name="toast"
55
+ state={state}
56
+ position={position}
57
+ {...regionProps}
58
+ >
59
+ {state.visibleToasts.map((toast) => (
60
+ <NotificationItem
61
+ key={toast.key}
62
+ name="toast"
63
+ toast={toast}
64
+ state={state}
65
+ itemRef={itemRef}
66
+ onHoverStart={onHoverStart}
67
+ onHoverEnd={onHoverEnd}
68
+ data-type={toast.content.type}
69
+ />
70
+ ))}
71
+ </NotificationRegion>
72
+ )
73
+ })
74
+
75
+ export default Toast
76
+
77
+ export function useToast(props: ToastProps = {}) {
78
+ 'use memo'
79
+
80
+ const toastHandlerRef = useRef<ToastHandler>(null)
81
+ const element = <Toast ref={toastHandlerRef} {...props} />
82
+ function show(message: ReactNode, options: ShowToastOptions) {
83
+ toastHandlerRef.current?.show(message, options)
84
+ }
85
+ return [element, show] as const
86
+ }
@@ -0,0 +1,47 @@
1
+ .charcoal-notification-region {
2
+ position: fixed;
3
+ left: 0;
4
+ right: 0;
5
+ display: grid;
6
+ justify-content: center;
7
+ pointer-events: none;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ .charcoal-notification {
12
+ box-sizing: border-box;
13
+ border-radius: var(--charcoal-radius-oval);
14
+ width: fit-content;
15
+ max-width: calc(100vw - var(--charcoal-space-46));
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ gap: var(--charcoal-space-20);
20
+ pointer-events: auto;
21
+ }
22
+
23
+ .charcoal-notification-content {
24
+ display: flex;
25
+ align-items: center;
26
+ min-width: 0;
27
+ }
28
+
29
+ .charcoal-notification-label {
30
+ min-width: 0;
31
+ overflow: hidden;
32
+ overflow-wrap: break-word;
33
+ display: -webkit-box;
34
+ -webkit-box-orient: vertical;
35
+ -webkit-line-clamp: 2;
36
+ font-size: var(--charcoal-text-font-size-caption-m);
37
+ font-weight: var(--charcoal-text-font-weight-bold);
38
+ line-height: var(--charcoal-text-line-height-caption-m);
39
+ }
40
+
41
+ @media (prefers-reduced-motion: reduce) {
42
+
43
+ .charcoal-notification, .charcoal-notification[data-exiting='true'] {
44
+ animation: none;
45
+ }
46
+ }
47
+
@@ -0,0 +1,46 @@
1
+ .charcoal-notification-region {
2
+ position: fixed;
3
+ left: 0;
4
+ right: 0;
5
+ display: grid;
6
+ justify-content: center;
7
+ pointer-events: none;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ .charcoal-notification {
12
+ box-sizing: border-box;
13
+ border-radius: var(--charcoal-radius-oval);
14
+ width: fit-content;
15
+ max-width: calc(100vw - var(--charcoal-space-46));
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ gap: var(--charcoal-space-20);
20
+ pointer-events: auto;
21
+ }
22
+
23
+ .charcoal-notification-content {
24
+ display: flex;
25
+ align-items: center;
26
+ min-width: 0;
27
+ }
28
+
29
+ .charcoal-notification-label {
30
+ min-width: 0;
31
+ overflow: hidden;
32
+ overflow-wrap: break-word;
33
+ display: -webkit-box;
34
+ -webkit-box-orient: vertical;
35
+ -webkit-line-clamp: 2;
36
+ font-size: var(--charcoal-text-font-size-caption-m);
37
+ font-weight: var(--charcoal-text-font-weight-bold);
38
+ line-height: var(--charcoal-text-line-height-caption-m);
39
+ }
40
+
41
+ @media (prefers-reduced-motion: reduce) {
42
+ .charcoal-notification,
43
+ .charcoal-notification[data-exiting='true'] {
44
+ animation: none;
45
+ }
46
+ }
@@ -0,0 +1,28 @@
1
+ export type NotificationName = 'snackbar' | 'toast'
2
+ export type Position = 'top' | 'bottom'
3
+ export type NotificationOrder = 'queue' | 'replace'
4
+
5
+ export type UseNotificationOptions = {
6
+ position?: Position
7
+ /**
8
+ * 画面端からの距離(ピクセル)
9
+ * @default 16
10
+ */
11
+ offset?: number
12
+ /**
13
+ * 通知を表示する時間(ミリ秒)。負の値は 0、数値でない値は 5000 として扱う
14
+ * @default 5000
15
+ */
16
+ duration?: number
17
+ /**
18
+ * 表示中の通知がある場合の次の通知の表示方法
19
+ * @default 'queue'
20
+ */
21
+ order?: NotificationOrder
22
+ }
23
+
24
+ export type NotificationProps = UseNotificationOptions & {
25
+ zIndex?: number
26
+ portalContainer?: HTMLElement
27
+ className?: string
28
+ }