@charcoal-ui/react-sandbox 1.0.0-alpha.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 (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +18 -0
  3. package/package.json +72 -0
  4. package/src/_lib/compat.ts +15 -0
  5. package/src/components/Carousel/index.story.tsx +86 -0
  6. package/src/components/Carousel/index.tsx +382 -0
  7. package/src/components/CarouselButton/index.story.tsx +44 -0
  8. package/src/components/CarouselButton/index.tsx +162 -0
  9. package/src/components/Filter/index.story.tsx +80 -0
  10. package/src/components/Filter/index.tsx +182 -0
  11. package/src/components/HintText/index.story.tsx +19 -0
  12. package/src/components/HintText/index.tsx +95 -0
  13. package/src/components/Layout/index.story.tsx +121 -0
  14. package/src/components/Layout/index.tsx +363 -0
  15. package/src/components/LeftMenu/index.tsx +68 -0
  16. package/src/components/MenuListItem/index.story.tsx +143 -0
  17. package/src/components/MenuListItem/index.tsx +226 -0
  18. package/src/components/Pager/index.story.tsx +102 -0
  19. package/src/components/Pager/index.tsx +255 -0
  20. package/src/components/Spinner/index.story.tsx +47 -0
  21. package/src/components/Spinner/index.tsx +86 -0
  22. package/src/components/SwitchCheckbox/index.story.tsx +32 -0
  23. package/src/components/SwitchCheckbox/index.tsx +147 -0
  24. package/src/components/TextEllipsis/helper.ts +57 -0
  25. package/src/components/TextEllipsis/index.story.tsx +41 -0
  26. package/src/components/TextEllipsis/index.tsx +35 -0
  27. package/src/components/WithIcon/index.story.tsx +145 -0
  28. package/src/components/WithIcon/index.tsx +158 -0
  29. package/src/components/icons/Base.tsx +75 -0
  30. package/src/components/icons/DotsIcon.tsx +33 -0
  31. package/src/components/icons/InfoIcon.tsx +30 -0
  32. package/src/components/icons/NextIcon.tsx +47 -0
  33. package/src/components/icons/WedgeIcon.tsx +57 -0
  34. package/src/foundation/contants.ts +6 -0
  35. package/src/foundation/hooks.ts +195 -0
  36. package/src/foundation/support.ts +29 -0
  37. package/src/foundation/utils.ts +31 -0
  38. package/src/index.ts +45 -0
  39. package/src/misc/storybook-helper.ts +17 -0
  40. package/src/styled.ts +3 -0
  41. package/src/type.d.ts +12 -0
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+ import styled from 'styled-components'
3
+
4
+ interface Props {
5
+ size?: number | string
6
+ subLink?: boolean
7
+ }
8
+
9
+ export { DotsIcon as default }
10
+
11
+ function DotsIcon({ size }: Props) {
12
+ return (
13
+ <StyledSVG viewBox="0 0 20 6" width={size} height={size}>
14
+ <path
15
+ fillRule="evenodd"
16
+ d={`M5,14.5 C3.61928813,14.5 2.5,13.3807119 2.5,12 C2.5,10.6192881 3.61928813,9.5 5,9.5
17
+ C6.38071187,9.5 7.5,10.6192881 7.5,12 C7.5,13.3807119 6.38071187,14.5 5,14.5 Z M12,14.5
18
+ C10.6192881,14.5 9.5,13.3807119 9.5,12 C9.5,10.6192881 10.6192881,9.5 12,9.5
19
+ C13.3807119,9.5 14.5,10.6192881 14.5,12 C14.5,13.3807119 13.3807119,14.5 12,14.5 Z M19,14.5
20
+ C17.6192881,14.5 16.5,13.3807119 16.5,12 C16.5,10.6192881 17.6192881,9.5 19,9.5
21
+ C20.3807119,9.5 21.5,10.6192881 21.5,12 C21.5,13.3807119 20.3807119,14.5 19,14.5 Z`}
22
+ transform="translate(-2 -9)"
23
+ />
24
+ </StyledSVG>
25
+ )
26
+ }
27
+ DotsIcon.defaultProps = {
28
+ size: 16,
29
+ }
30
+
31
+ const StyledSVG = styled.svg`
32
+ fill: currentColor;
33
+ `
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+ import styled from 'styled-components'
3
+ import IconBase from './Base'
4
+
5
+ const size = 16
6
+
7
+ export default function InfoIcon() {
8
+ const path = (
9
+ <>
10
+ <path
11
+ d="M8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183
12
+ 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16Z"
13
+ />
14
+ <Path
15
+ d="M14 8C14 11.3137 11.3137 14 8 14C4.68629 14 2 11.3137 2 8C2 4.68629
16
+ 4.68629 2 8 2C11.3137 2 14 4.68629 14 8ZM8 6.25C8.69036 6.25 9.25 5.69036
17
+ 9.25 5C9.25 4.30964 8.69036 3.75 8 3.75C7.30964 3.75 6.75 4.30964 6.75
18
+ 5C6.75 5.69036 7.30964 6.25 8 6.25ZM7 7.75V11.25C7 11.8023 7.44772 12.25
19
+ 8 12.25C8.55228 12.25 9 11.8023 9 11.25V7.75C9 7.19772 8.55228 6.75 8
20
+ 6.75C7.44772 6.75 7 7.19772 7 7.75Z"
21
+ />
22
+ </>
23
+ )
24
+ return <IconBase viewBoxSize={size} size={size} currentColor path={path} />
25
+ }
26
+
27
+ const Path = styled.path`
28
+ fill: ${({ theme }) => theme.color.surface1};
29
+ fill-rule: evenodd;
30
+ `
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { unreachable } from '../../foundation/utils'
3
+ import IconBase from './Base'
4
+
5
+ export enum WedgeDirection {
6
+ Up = 'up',
7
+ Down = 'down',
8
+ Left = 'left',
9
+ Right = 'right',
10
+ }
11
+
12
+ // eslint-disable-next-line max-len
13
+ const path = `M8.08579 16.5858C7.30474 17.3668 7.30474 18.6332 8.08579 19.4142C8.86684 20.1953 10.1332 20.1953 10.9142 19.4142L18.3284 12L10.9142 4.58579C10.1332 3.80474 8.86684 3.80474 8.08579 4.58579C7.30474 5.36684 7.30474 6.63317 8.08579 7.41421L12.6716 12L8.08579 16.5858Z`
14
+ const size = 24
15
+
16
+ interface Props {
17
+ direction: WedgeDirection
18
+ }
19
+
20
+ export default function NextIcon({ direction }: Props) {
21
+ const transform = directionToTransform(direction)
22
+ return (
23
+ <IconBase
24
+ viewBoxSize={size}
25
+ size={size}
26
+ currentColor
27
+ path={path}
28
+ transform={transform}
29
+ />
30
+ )
31
+ }
32
+
33
+ function directionToTransform(direction: WedgeDirection) {
34
+ // "5 4" is the center point of the "0 0 10 8" viewBox
35
+ switch (direction) {
36
+ case WedgeDirection.Up:
37
+ return 'rotate(270 12 12)'
38
+ case WedgeDirection.Down:
39
+ return 'rotate(90 12 12)'
40
+ case WedgeDirection.Left:
41
+ return 'rotate(180 12 12)'
42
+ case WedgeDirection.Right:
43
+ return undefined
44
+ default:
45
+ return unreachable(direction)
46
+ }
47
+ }
@@ -0,0 +1,57 @@
1
+ import React from 'react'
2
+ import styled from 'styled-components'
3
+
4
+ import { unreachable } from '../../foundation/utils'
5
+
6
+ export enum WedgeDirection {
7
+ Up = 'up',
8
+ Down = 'down',
9
+ Left = 'left',
10
+ Right = 'right',
11
+ }
12
+
13
+ interface Props {
14
+ size?: number | string
15
+ direction: WedgeDirection
16
+ }
17
+
18
+ export default function WedgeIcon({ size, direction }: Props) {
19
+ return (
20
+ // NOTE: directionToTransform depends on the value of viewBox
21
+ <svg viewBox="0 0 10 8" width={size} height={size}>
22
+ <StyledPolyline
23
+ strokeWidth="2"
24
+ points="1,2 5,6 9,2"
25
+ transform={directionToTransform(direction)}
26
+ />
27
+ </svg>
28
+ )
29
+ }
30
+ WedgeIcon.defaultProps = {
31
+ size: 16,
32
+ white: false,
33
+ lightGray: false,
34
+ }
35
+
36
+ function directionToTransform(direction: WedgeDirection) {
37
+ // "5 4" is the center point of the "0 0 10 8" viewBox
38
+ switch (direction) {
39
+ case WedgeDirection.Up:
40
+ return 'rotate(180 5 4)'
41
+ case WedgeDirection.Down:
42
+ return undefined
43
+ case WedgeDirection.Left:
44
+ return 'rotate(90 5 4)'
45
+ case WedgeDirection.Right:
46
+ return 'rotate(-90 5 4)'
47
+ default:
48
+ return unreachable(direction)
49
+ }
50
+ }
51
+
52
+ const StyledPolyline = styled.polyline`
53
+ fill: none;
54
+ stroke-linejoin: round;
55
+ stroke-linecap: round;
56
+ stroke: currentColor;
57
+ `
@@ -0,0 +1,6 @@
1
+ import { columnPx, GUTTER_UNIT } from '@charcoal-ui/foundation'
2
+
3
+ export const MAIN_COLUMN_HORIZONTAL_MIN_MARGIN = 72
4
+
5
+ export const RESPONSIVE_LEFT_WIDTH = columnPx(2) + GUTTER_UNIT
6
+ export const RESPONSIVE_MAIN_MAX_WIDTH = columnPx(12)
@@ -0,0 +1,195 @@
1
+ import {
2
+ useCallback,
3
+ useDebugValue,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ useReducer,
7
+ useRef,
8
+ useState,
9
+ } from 'react'
10
+ import ReactDOM from 'react-dom'
11
+ import { useTheme } from 'styled-components'
12
+ import { maxWidth } from '@charcoal-ui/utils'
13
+
14
+ declare const __TEST__: object | undefined // actually object|false, but using undefined allows ! assertion
15
+
16
+ declare module 'react-dom' {
17
+ export function flushSync<R>(callback: () => R): R
18
+ }
19
+
20
+ /**
21
+ * 現在の画面幅がモバイル幅かどうかを返す
22
+ */
23
+ export function useMediaScreen1() {
24
+ return useMedia(maxWidth(useTheme().breakpoint.screen1))
25
+ }
26
+
27
+ /**
28
+ * Returns a dynamically-updating media query result.
29
+ *
30
+ * When the media query's matching state changes, this hook's result
31
+ * will update with sync priority.
32
+ *
33
+ * @param query A full media query (the string written between `@media` and the `{` in CSS)
34
+ * @returns true if the query matches, false if it doesn't
35
+ */
36
+ export function useMedia(query: string) {
37
+ const matcher = useMemo(
38
+ () =>
39
+ __TEST__
40
+ ? {
41
+ matches: false,
42
+ addListener: () => {
43
+ // do nothing
44
+ },
45
+ removeListener: () => {
46
+ // do nothing
47
+ },
48
+ }
49
+ : matchMedia(query),
50
+ [query]
51
+ )
52
+ const [matches, setMatches] = useState<boolean>(matcher.matches)
53
+
54
+ // can only happen if/when the query changes
55
+ if (matcher.matches !== matches) {
56
+ setMatches(matcher.matches)
57
+ }
58
+
59
+ const callback = (e: MediaQueryListEvent) => {
60
+ // We're not on a React event listener, so React doesn't know the priority of the setState call
61
+ // Media query updates _are_ very high priority to avoid FOUC
62
+ // so we need to emit a sync priority update
63
+ try {
64
+ // However, flushSync may throw if the matcher is triggered by a
65
+ // forced relayout that happens during a React lifecycle handler.
66
+ // Try to be resilient and retry without flushSync if flushSync throws.
67
+ ReactDOM.flushSync(() => {
68
+ setMatches(e.matches)
69
+ })
70
+ } catch {
71
+ setMatches(e.matches)
72
+ }
73
+ }
74
+
75
+ useLayoutEffect(() => {
76
+ matcher.addListener(callback)
77
+ // sync update
78
+ setMatches(matcher.matches)
79
+ return () => {
80
+ matcher.removeListener(callback)
81
+ }
82
+ }, [matcher])
83
+
84
+ useDebugValue(`${query}: ${matches.toString()}`)
85
+
86
+ return matches
87
+ }
88
+
89
+ export interface ElementSize {
90
+ width: number
91
+ height: number
92
+ }
93
+
94
+ function measure(ref: Element | null): ElementSize | undefined {
95
+ return ref !== null ? ref.getBoundingClientRect() : undefined
96
+ }
97
+
98
+ export function useElementSize(
99
+ ref: React.RefObject<Element>,
100
+ deps: any[] = []
101
+ ) {
102
+ // _don't_ call measure synchronously here even if you somehow can
103
+ // measurement has to be done outside the render phase, either
104
+ // as the resize observer callback or as a layout effect
105
+
106
+ const [size, setSize] = useReducer(
107
+ (
108
+ state: ElementSize | undefined,
109
+ next: ElementSize | undefined
110
+ ): ElementSize | undefined => {
111
+ // width, height, etc are not own properties but getters in the prototype
112
+ // can't use shallowEqual or other iterative checks
113
+ if (state === undefined || next === undefined) {
114
+ return next
115
+ }
116
+ if (state.height === next.height && state.width === next.width) {
117
+ return state
118
+ }
119
+ return next
120
+ },
121
+ undefined
122
+ )
123
+ const [watch, setWatch] = useState<Element | null>(null)
124
+ useLayoutEffect(() => {
125
+ if (watch === null) {
126
+ return
127
+ }
128
+
129
+ const observer = new ResizeObserver(() => {
130
+ // NOTE: the ResizeObserverCallback receives a rect,
131
+ // but it's not measured in the same way as getBoundingClientRect,
132
+ // which causes unstable layout
133
+ const newSize = measure(watch)
134
+ setSize(newSize)
135
+ })
136
+
137
+ // The ResizeObserver is supposed to call handleResize on observe
138
+ observer.observe(watch)
139
+
140
+ return () => {
141
+ // this will correctly unobserve if either the observer
142
+ // or the current changes, and even on unmount
143
+ // no need for an observer.disconnect() 🎉
144
+ observer.unobserve(watch)
145
+ setSize(undefined)
146
+ }
147
+ }, [watch])
148
+
149
+ // eslint-disable-next-line react-hooks/exhaustive-deps
150
+ useLayoutEffect(() => {
151
+ if (ref.current !== watch) {
152
+ setWatch(ref.current)
153
+ }
154
+ })
155
+
156
+ useLayoutEffect(() => {
157
+ if (deps.length > 0) {
158
+ // Sync measuring
159
+ setSize(measure(ref.current))
160
+ }
161
+ // eslint-disable-next-line react-hooks/exhaustive-deps
162
+ }, deps)
163
+
164
+ useDebugValue(size)
165
+
166
+ return size
167
+ }
168
+
169
+ /**
170
+ * Debounce version of setState with `requestAnimationFrame`
171
+ *
172
+ * @param defaultValue Default value for `useState`
173
+ */
174
+ export function useDebounceAnimationState<T>(defaultValue: T) {
175
+ const [state, setState] = useState(defaultValue)
176
+ const timer = useRef<ReturnType<typeof requestAnimationFrame>>()
177
+ // typescript bug? (any when omitting type annotation)
178
+ // eslint-disable-next-line @typescript-eslint/no-inferrable-types
179
+ const setDebounceState = useCallback((value: T, force: boolean = false) => {
180
+ if (force) {
181
+ setState(value)
182
+ return
183
+ }
184
+ if (timer.current !== undefined) {
185
+ return
186
+ }
187
+ timer.current = requestAnimationFrame(() => {
188
+ setState(value)
189
+ if (timer.current !== undefined) {
190
+ timer.current = undefined
191
+ }
192
+ })
193
+ }, [])
194
+ return [state, setDebounceState] as [typeof state, typeof setDebounceState]
195
+ }
@@ -0,0 +1,29 @@
1
+ var passiveEventsResult: boolean | undefined // eslint-disable-line no-var
2
+ export function passiveEvents(): boolean {
3
+ if (passiveEventsResult !== undefined) {
4
+ return passiveEventsResult
5
+ }
6
+
7
+ passiveEventsResult = false
8
+ try {
9
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
10
+ const options = Object.defineProperty({}, 'passive', {
11
+ get() {
12
+ return (passiveEventsResult = true)
13
+ },
14
+ })
15
+
16
+ window.addEventListener('test', test, options)
17
+ window.removeEventListener('test', test)
18
+ } catch {
19
+ // test fail
20
+ }
21
+
22
+ return passiveEventsResult
23
+
24
+ function test() {
25
+ /* empty */
26
+ }
27
+ }
28
+
29
+ export const isEdge = navigator.userAgent.includes('Edge/')
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Function used to assert a given code path is unreachable
3
+ */
4
+ export function unreachable(): never
5
+ /**
6
+ * Function used to assert a given code path is unreachable.
7
+ * Very useful for ensuring switches are exhaustive:
8
+ *
9
+ * ```ts
10
+ * switch (a.type) {
11
+ * case Types.A:
12
+ * case Types.B:
13
+ * break
14
+ * default:
15
+ * unreachable(a) // will cause a build error if there was
16
+ * // a Types.C that was not checked
17
+ * }
18
+ * ```
19
+ *
20
+ * @param value Value to be asserted as unreachable
21
+ */
22
+ // NOTE: Uses separate overloads, _not_ `value?: never`, to not allow `undefined` to be passed
23
+ // eslint-disable-next-line @typescript-eslint/unified-signatures
24
+ export function unreachable(value: never): never
25
+ export function unreachable(value?: never): never {
26
+ throw new Error(
27
+ arguments.length === 0
28
+ ? 'unreachable'
29
+ : `unreachable (${JSON.stringify(value)})`
30
+ )
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ export {
2
+ default as Filter,
3
+ FilterButton,
4
+ FilterIconButton,
5
+ FilterLink,
6
+ } from './components/Filter'
7
+ export { default as HintText } from './components/HintText'
8
+ export {
9
+ default as Layout,
10
+ CancelLayoutItemBodyPadding,
11
+ LAYOUT_ITEM_BODY_PADDING,
12
+ LayoutItem,
13
+ LayoutItemBody,
14
+ LayoutItemHeader,
15
+ StyledCancelLayoutItemBodyPadding,
16
+ StyledLayoutItemBody,
17
+ useLayoutItemBodyPadding,
18
+ } from './components/Layout'
19
+ export { default as LeftMenu, LeftMenuContent } from './components/LeftMenu'
20
+ export {
21
+ default as MenuListItem,
22
+ type MenuListItemBaseData,
23
+ MenuListItemContext,
24
+ MenuListItemWithIcon,
25
+ MenuListLabel,
26
+ MenuListLinkItem,
27
+ MenuListLinkItemWithIcon,
28
+ MenuListSpacer,
29
+ } from './components/MenuListItem'
30
+ export { default as SwitchCheckbox } from './components/SwitchCheckbox'
31
+ export { TextEllipsis } from './components/TextEllipsis'
32
+ export { default as WithIcon } from './components/WithIcon'
33
+ export {
34
+ ComponentAbstraction,
35
+ useComponentAbstraction,
36
+ } from '@charcoal-ui/react'
37
+ export {
38
+ MAIN_COLUMN_HORIZONTAL_MIN_MARGIN,
39
+ RESPONSIVE_LEFT_WIDTH,
40
+ RESPONSIVE_MAIN_MAX_WIDTH,
41
+ } from './foundation/contants'
42
+ export { default as Carousel } from './components/Carousel'
43
+ export { useElementSize, useMedia, useMediaScreen1 } from './foundation/hooks'
44
+ export { default as Pager, LinkPager } from './components/Pager'
45
+ export { default as Spinner, SpinnerIcon } from './components/Spinner'
@@ -0,0 +1,17 @@
1
+ import styled, { css } from 'styled-components'
2
+
3
+ export const dummyText = css`
4
+ color: ${({ theme }) => theme.color.text4};
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ font-size: 14px;
9
+ font-weight: bold;
10
+ `
11
+
12
+ export const Dummy = styled.div`
13
+ background-color: ${({ theme }) => theme.color.surface2};
14
+ border-radius: 8px;
15
+
16
+ ${dummyText}
17
+ `
package/src/styled.ts ADDED
@@ -0,0 +1,3 @@
1
+ import styled from 'styled-components'
2
+ import createTheme from '@charcoal-ui/styled'
3
+ export const theme = createTheme(styled)
package/src/type.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { ElementsTheme, ThemeProp } from '@charcoal-ui/styled'
2
+ import { CSSProp, DefaultTheme } from 'styled-components'
3
+
4
+ declare module 'react' {
5
+ interface Attributes {
6
+ css?: CSSProp<DefaultTheme> | ThemeProp<DefaultTheme>
7
+ }
8
+ }
9
+
10
+ declare module 'styled-components' {
11
+ export interface DefaultTheme extends ElementsTheme {}
12
+ }