@sakura-ui/sakura-ui 0.2.0 → 0.3.0

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 (215) hide show
  1. package/README.md +16 -6
  2. package/package.json +21 -15
  3. package/packages/core/package.json +26 -15
  4. package/packages/core/src/components/Button.tsx +33 -32
  5. package/packages/core/src/components/Card.tsx +29 -17
  6. package/packages/core/src/components/Code.tsx +6 -4
  7. package/packages/core/src/components/Faq.tsx +13 -7
  8. package/packages/core/src/components/Heading.tsx +42 -32
  9. package/packages/core/src/components/Icon.tsx +43 -13
  10. package/packages/core/src/components/IconButton.tsx +54 -43
  11. package/packages/core/src/components/LangSelector.tsx +135 -0
  12. package/packages/core/src/components/Link.tsx +38 -66
  13. package/packages/core/src/components/LinkCard.tsx +91 -0
  14. package/packages/core/src/components/List.tsx +14 -12
  15. package/packages/core/src/components/MenuButton.tsx +112 -0
  16. package/packages/core/src/components/NavigationItem.tsx +41 -0
  17. package/packages/core/src/components/OverflowContainer.tsx +24 -0
  18. package/packages/core/src/components/PopoverMenu.tsx +81 -0
  19. package/packages/core/src/components/Pre.tsx +6 -4
  20. package/packages/core/src/components/Table.tsx +31 -25
  21. package/packages/core/src/components/buttonStyle.ts +42 -31
  22. package/packages/core/src/components/index.ts +13 -25
  23. package/packages/core/src/icons/Close.tsx +21 -0
  24. package/packages/core/src/icons/CloseMobile.tsx +43 -0
  25. package/packages/core/src/icons/KeyboardArrowDown.tsx +24 -0
  26. package/packages/core/src/icons/Language.tsx +25 -0
  27. package/packages/core/src/icons/LanguageMobile.tsx +40 -0
  28. package/packages/core/src/icons/Menu.tsx +24 -0
  29. package/packages/core/src/icons/MenuMobile.tsx +40 -0
  30. package/packages/core/src/icons/index.ts +8 -0
  31. package/packages/core/src/icons/props.ts +5 -0
  32. package/packages/core/src/index.ts +9 -28
  33. package/packages/core/src/type.d.ts +1 -0
  34. package/packages/core/tests/Button.test.tsx +20 -10
  35. package/packages/core/tests/Card.test.tsx +16 -11
  36. package/packages/core/tests/IconButton.test.tsx +57 -0
  37. package/packages/core/tests/Link.test.tsx +60 -0
  38. package/packages/core/tests/vitest.setup.ts +1 -0
  39. package/packages/core/tsconfig.node.json +3 -2
  40. package/packages/forms/.turbo/turbo-build.log +22 -0
  41. package/packages/forms/dist/index.cjs.js +199 -120
  42. package/packages/forms/dist/index.es.js +890 -1023
  43. package/packages/forms/dist/types/components/Checkbox.d.ts +9 -4
  44. package/packages/forms/dist/types/components/Checkbox.d.ts.map +1 -1
  45. package/packages/forms/dist/types/components/FieldsetControl.d.ts +14 -9
  46. package/packages/forms/dist/types/components/FieldsetControl.d.ts.map +1 -1
  47. package/packages/forms/dist/types/components/FileInput.d.ts +10 -3
  48. package/packages/forms/dist/types/components/FileInput.d.ts.map +1 -1
  49. package/packages/forms/dist/types/components/Input.d.ts +9 -4
  50. package/packages/forms/dist/types/components/Input.d.ts.map +1 -1
  51. package/packages/forms/dist/types/components/LabelControl.d.ts +13 -8
  52. package/packages/forms/dist/types/components/LabelControl.d.ts.map +1 -1
  53. package/packages/forms/dist/types/components/Radio.d.ts +9 -4
  54. package/packages/forms/dist/types/components/Radio.d.ts.map +1 -1
  55. package/packages/forms/dist/types/components/Select.d.ts +9 -4
  56. package/packages/forms/dist/types/components/Select.d.ts.map +1 -1
  57. package/packages/forms/dist/types/components/Textarea.d.ts +8 -3
  58. package/packages/forms/dist/types/components/Textarea.d.ts.map +1 -1
  59. package/packages/forms/dist/types/components/controlled/CheckboxGroup.d.ts +16 -11
  60. package/packages/forms/dist/types/components/controlled/CheckboxGroup.d.ts.map +1 -1
  61. package/packages/forms/dist/types/components/controlled/RadioGroup.d.ts +17 -12
  62. package/packages/forms/dist/types/components/controlled/RadioGroup.d.ts.map +1 -1
  63. package/packages/forms/dist/types/components/index.d.ts +8 -8
  64. package/packages/forms/dist/types/components/index.d.ts.map +1 -1
  65. package/packages/forms/dist/types/components/inputStyle.d.ts +1 -1
  66. package/packages/forms/dist/types/components/inputStyle.d.ts.map +1 -1
  67. package/packages/forms/dist/types/index.d.ts +0 -1
  68. package/packages/forms/dist/types/index.d.ts.map +1 -1
  69. package/packages/forms/node_modules/.bin/autoprefixer +2 -2
  70. package/packages/forms/node_modules/.bin/tailwind +2 -2
  71. package/packages/forms/node_modules/.bin/tailwindcss +2 -2
  72. package/packages/forms/node_modules/.bin/ts-node +4 -4
  73. package/packages/forms/node_modules/.bin/ts-node-cwd +4 -4
  74. package/packages/forms/node_modules/.bin/ts-node-esm +4 -4
  75. package/packages/forms/node_modules/.bin/ts-node-script +4 -4
  76. package/packages/forms/node_modules/.bin/ts-node-transpile-only +4 -4
  77. package/packages/forms/node_modules/.bin/ts-script +4 -4
  78. package/packages/forms/node_modules/.bin/tsc +17 -0
  79. package/packages/forms/node_modules/.bin/tsserver +17 -0
  80. package/packages/forms/node_modules/.vite/vitest/results.json +1 -0
  81. package/packages/forms/package.json +17 -14
  82. package/packages/forms/src/components/Checkbox.tsx +76 -80
  83. package/packages/forms/src/components/FieldsetControl.tsx +21 -28
  84. package/packages/forms/src/components/FileInput.tsx +87 -63
  85. package/packages/forms/src/components/Input.tsx +29 -31
  86. package/packages/forms/src/components/LabelControl.tsx +19 -25
  87. package/packages/forms/src/components/Radio.tsx +66 -69
  88. package/packages/forms/src/components/Select.tsx +65 -65
  89. package/packages/forms/src/components/Textarea.tsx +49 -54
  90. package/packages/forms/src/components/controlled/CheckboxGroup.tsx +15 -17
  91. package/packages/forms/src/components/controlled/RadioGroup.tsx +16 -18
  92. package/packages/forms/src/components/index.ts +8 -8
  93. package/packages/forms/src/components/inputStyle.ts +7 -8
  94. package/packages/forms/src/index.ts +0 -11
  95. package/packages/forms/tests/Checkbox.test.tsx +44 -0
  96. package/packages/forms/tests/FieldsetControl.test.tsx +62 -11
  97. package/packages/forms/tests/FileInput.test.tsx +25 -0
  98. package/packages/forms/tests/Input.test.tsx +12 -83
  99. package/packages/forms/tests/LabelControl.test.tsx +82 -20
  100. package/packages/forms/tests/Radio.test.tsx +23 -78
  101. package/packages/forms/tests/Select.test.tsx +30 -0
  102. package/packages/forms/tests/Textarea.test.tsx +25 -0
  103. package/packages/forms/tests/vitest.setup.ts +1 -0
  104. package/packages/forms/tsconfig.node.json +3 -2
  105. package/packages/helper/.turbo/turbo-build.log +23 -0
  106. package/packages/helper/dist/index.cjs.js +78 -0
  107. package/packages/helper/dist/index.es.js +119 -0
  108. package/packages/helper/dist/types/calc.d.ts +2 -0
  109. package/packages/helper/dist/types/calc.d.ts.map +1 -0
  110. package/packages/{forms/dist/types/utils/class.d.ts → helper/dist/types/cx.d.ts} +1 -1
  111. package/packages/helper/dist/types/cx.d.ts.map +1 -0
  112. package/packages/helper/dist/types/index.d.ts +6 -0
  113. package/packages/helper/dist/types/index.d.ts.map +1 -0
  114. package/packages/helper/dist/types/querySelector.d.ts +2 -0
  115. package/packages/helper/dist/types/querySelector.d.ts.map +1 -0
  116. package/packages/helper/dist/types/styles.d.ts +22 -0
  117. package/packages/helper/dist/types/styles.d.ts.map +1 -0
  118. package/packages/helper/dist/types/treefy.d.ts +8 -0
  119. package/packages/helper/dist/types/treefy.d.ts.map +1 -0
  120. package/packages/helper/package.json +40 -0
  121. package/packages/helper/src/calc.ts +1 -0
  122. package/packages/helper/src/index.ts +5 -0
  123. package/packages/helper/src/querySelector.ts +20 -0
  124. package/packages/helper/src/styles.ts +116 -0
  125. package/packages/helper/src/treefy.ts +22 -0
  126. package/packages/helper/tsconfig.json +13 -0
  127. package/packages/helper/tsconfig.node.json +6 -0
  128. package/packages/helper/vite.config.ts +26 -0
  129. package/packages/markdown/.turbo/turbo-build.log +22 -0
  130. package/packages/markdown/README.md +129 -0
  131. package/packages/markdown/dist/index.cjs.js +175 -0
  132. package/packages/markdown/dist/index.es.js +17098 -0
  133. package/packages/markdown/dist/types/components/Markdown.d.ts +15 -0
  134. package/packages/markdown/dist/types/components/Markdown.d.ts.map +1 -0
  135. package/packages/markdown/dist/types/components/index.d.ts +2 -0
  136. package/packages/markdown/dist/types/components/index.d.ts.map +1 -0
  137. package/packages/markdown/dist/types/index.d.ts +2 -0
  138. package/packages/markdown/dist/types/index.d.ts.map +1 -0
  139. package/packages/markdown/dist/types/plugins/attr.d.ts +3 -0
  140. package/packages/markdown/dist/types/plugins/attr.d.ts.map +1 -0
  141. package/packages/markdown/dist/types/plugins/card.d.ts +3 -0
  142. package/packages/markdown/dist/types/plugins/card.d.ts.map +1 -0
  143. package/packages/markdown/dist/types/plugins/cell.d.ts +3 -0
  144. package/packages/markdown/dist/types/plugins/cell.d.ts.map +1 -0
  145. package/packages/markdown/dist/types/plugins/faq.d.ts +3 -0
  146. package/packages/markdown/dist/types/plugins/faq.d.ts.map +1 -0
  147. package/packages/markdown/dist/types/plugins/grid.d.ts +3 -0
  148. package/packages/markdown/dist/types/plugins/grid.d.ts.map +1 -0
  149. package/packages/markdown/dist/types/plugins/headings.d.ts +9 -0
  150. package/packages/markdown/dist/types/plugins/headings.d.ts.map +1 -0
  151. package/packages/markdown/dist/types/plugins/helper.d.ts +9 -0
  152. package/packages/markdown/dist/types/plugins/helper.d.ts.map +1 -0
  153. package/packages/markdown/dist/types/plugins/index.d.ts +9 -0
  154. package/packages/markdown/dist/types/plugins/index.d.ts.map +1 -0
  155. package/packages/markdown/dist/types/plugins/linkButton.d.ts +3 -0
  156. package/packages/markdown/dist/types/plugins/linkButton.d.ts.map +1 -0
  157. package/packages/markdown/dist/types/plugins/youtube.d.ts +3 -0
  158. package/packages/markdown/dist/types/plugins/youtube.d.ts.map +1 -0
  159. package/packages/markdown/node_modules/.bin/autoprefixer +17 -0
  160. package/packages/markdown/node_modules/.bin/tailwind +17 -0
  161. package/packages/markdown/node_modules/.bin/tailwindcss +17 -0
  162. package/packages/markdown/node_modules/.bin/ts-node +17 -0
  163. package/packages/markdown/node_modules/.bin/ts-node-cwd +17 -0
  164. package/packages/markdown/node_modules/.bin/ts-node-esm +17 -0
  165. package/packages/markdown/node_modules/.bin/ts-node-script +17 -0
  166. package/packages/markdown/node_modules/.bin/ts-node-transpile-only +17 -0
  167. package/packages/markdown/node_modules/.bin/ts-script +17 -0
  168. package/packages/markdown/node_modules/.bin/tsc +17 -0
  169. package/packages/markdown/node_modules/.bin/tsserver +17 -0
  170. package/packages/markdown/package.json +83 -0
  171. package/packages/markdown/src/components/Markdown.tsx +361 -0
  172. package/packages/markdown/src/components/index.ts +1 -0
  173. package/packages/markdown/src/global.d.ts +9 -0
  174. package/packages/markdown/src/index.ts +1 -0
  175. package/packages/markdown/src/plugins/attr.ts +19 -0
  176. package/packages/markdown/src/plugins/card.ts +57 -0
  177. package/packages/markdown/src/plugins/cell.ts +47 -0
  178. package/packages/markdown/src/plugins/faq.ts +40 -0
  179. package/packages/markdown/src/plugins/grid.ts +51 -0
  180. package/packages/markdown/src/plugins/headings.ts +35 -0
  181. package/packages/markdown/src/plugins/helper.ts +27 -0
  182. package/packages/markdown/src/plugins/index.ts +8 -0
  183. package/packages/markdown/src/plugins/linkButton.ts +25 -0
  184. package/packages/markdown/src/plugins/youtube.ts +48 -0
  185. package/packages/markdown/tsconfig.json +13 -0
  186. package/packages/markdown/tsconfig.node.json +7 -0
  187. package/packages/markdown/vite.config.ts +30 -0
  188. package/packages/tailwind-theme-plugin/.turbo/turbo-build.log +22 -0
  189. package/packages/tailwind-theme-plugin/dist/index.cjs.js +1 -1
  190. package/packages/tailwind-theme-plugin/dist/index.es.js +5354 -6111
  191. package/packages/tailwind-theme-plugin/dist/types/index.d.ts +1 -1
  192. package/packages/tailwind-theme-plugin/dist/types/index.d.ts.map +1 -1
  193. package/packages/tailwind-theme-plugin/node_modules/.bin/autoprefixer +2 -2
  194. package/packages/tailwind-theme-plugin/node_modules/.bin/tailwind +2 -2
  195. package/packages/tailwind-theme-plugin/node_modules/.bin/tailwindcss +2 -2
  196. package/packages/tailwind-theme-plugin/node_modules/.bin/ts-node +4 -4
  197. package/packages/tailwind-theme-plugin/node_modules/.bin/ts-node-cwd +4 -4
  198. package/packages/tailwind-theme-plugin/node_modules/.bin/ts-node-esm +4 -4
  199. package/packages/tailwind-theme-plugin/node_modules/.bin/ts-node-script +4 -4
  200. package/packages/tailwind-theme-plugin/node_modules/.bin/ts-node-transpile-only +4 -4
  201. package/packages/tailwind-theme-plugin/node_modules/.bin/ts-script +4 -4
  202. package/packages/tailwind-theme-plugin/node_modules/.bin/tsc +17 -0
  203. package/packages/tailwind-theme-plugin/node_modules/.bin/tsserver +17 -0
  204. package/packages/tailwind-theme-plugin/package.json +6 -6
  205. package/packages/tailwind-theme-plugin/src/index.ts +226 -291
  206. package/packages/core/src/libs/forward-ref.ts +0 -44
  207. package/packages/core/src/types/component.ts +0 -83
  208. package/packages/forms/dist/types/utils/class.d.ts.map +0 -1
  209. package/packages/forms/node_modules/.bin/acorn +0 -17
  210. package/packages/forms/node_modules/.bin/browserslist +0 -17
  211. package/packages/forms/node_modules/.bin/eslint +0 -17
  212. package/packages/forms/node_modules/.bin/vitest +0 -17
  213. package/packages/forms/src/utils/class.ts +0 -2
  214. package/packages/tailwind-theme-plugin/node_modules/.bin/browserslist +0 -17
  215. /package/packages/{core/src/libs → helper/src}/cx.ts +0 -0
@@ -1,33 +1,43 @@
1
- import { cx } from '../libs/cx'
2
- import type { ComponentWithAs } from '../types/component'
3
- import { forwardRef } from '../libs/forward-ref'
4
- import { base, getVariantStyle, getSizeStyle } from './buttonStyle'
1
+ import React, { type ComponentProps } from 'react'
2
+ import { cx } from '@sakura-ui/helper'
3
+ import {
4
+ base,
5
+ getVariantStyle,
6
+ getSizeStyle,
7
+ type ButtonVariant,
8
+ type ButtonSize
9
+ } from './buttonStyle'
5
10
 
6
- export interface IconButtonProps {
7
- variant?: 'primary' | 'secondary'
8
- size?: 'lg' | 'md' | 'sm' | 'xs'
9
- iconLayout?: 'left' | 'right'
10
- icon: string
11
- rounded?: string
11
+ export namespace IconButton {
12
+ export interface Props<T extends React.ElementType> {
13
+ as?: T
14
+ variant?: ButtonVariant
15
+ size?: ButtonSize
16
+ iconLayout?: 'left' | 'right'
17
+ icon: string
18
+ rounded?: string
19
+ }
12
20
  }
13
21
 
14
- export const IconButton: ComponentWithAs<'button', IconButtonProps> =
15
- forwardRef((props, ref) => {
16
- const {
17
- as: Component = 'button',
18
- variant = 'primary',
19
- size = 'lg',
20
- iconLayout = 'left',
21
- icon,
22
- rounded,
23
- className,
24
- children,
25
- ...restProps
26
- } = props
22
+ export const IconButton = <T extends React.ElementType = 'button'>(
23
+ props: IconButton.Props<T> &
24
+ Omit<React.ComponentProps<T>, keyof IconButton.Props<T>>
25
+ ) => {
26
+ const {
27
+ as: Component = 'button',
28
+ variant = 'solid-fill',
29
+ size = 'lg',
30
+ iconLayout = 'left',
31
+ icon,
32
+ rounded,
33
+ className,
34
+ children,
35
+ ...restProps
36
+ } = props
27
37
 
28
- const styleFontSize = `${size === 'xs' ? 'scale-105' : 'scale-125'}`
38
+ const styleFontSize = `${size === 'xs' ? 'scale-105' : 'scale-125'}`
29
39
 
30
- const styleIcon = `
40
+ const styleIcon = `
31
41
  inline-block
32
42
  align-middle
33
43
  mb-1
@@ -37,23 +47,23 @@ export const IconButton: ComponentWithAs<'button', IconButtonProps> =
37
47
  antialiased
38
48
  `
39
49
 
40
- const styleRounded = `${rounded ? 'aspect-square !rounded-full !py-0' : ''}`
50
+ const styleRounded = `${rounded ? 'aspect-square !rounded-full !py-0' : ''}`
41
51
 
42
- // When text is specified on a button, set the 'aria-hidden' attribute of the icon to true.
43
- const ariaHidden = !!children
52
+ // When text is specified on a button, set the 'aria-hidden' attribute of the icon to true.
53
+ const ariaHidden = !!children
44
54
 
45
- return (
46
- <Component
47
- className={cx(
48
- base,
49
- getVariantStyle(variant),
50
- getSizeStyle(size),
51
- styleRounded,
52
- className
53
- )}
54
- {...restProps}
55
- ref={ref}
56
- >
55
+ return (
56
+ <Component
57
+ className={cx(
58
+ base,
59
+ getVariantStyle(variant),
60
+ getSizeStyle(size),
61
+ styleRounded,
62
+ className
63
+ )}
64
+ {...restProps}
65
+ >
66
+ <span>
57
67
  {iconLayout === 'left' && (
58
68
  <span
59
69
  className={cx(styleIcon, styleFontSize)}
@@ -71,8 +81,9 @@ export const IconButton: ComponentWithAs<'button', IconButtonProps> =
71
81
  {icon}
72
82
  </span>
73
83
  )}
74
- </Component>
75
- )
76
- })
84
+ </span>
85
+ </Component>
86
+ )
87
+ }
77
88
 
78
89
  IconButton.displayName = 'IconButton'
@@ -0,0 +1,135 @@
1
+ import React from 'react'
2
+ import { cx, mod, focusableSelector, Style } from '@sakura-ui/helper'
3
+ import { Language, LanguageMobile, KeyboadArrowDown } from '@/icons'
4
+ import { PopoverMenu, PopoverMenuItem } from './PopoverMenu'
5
+
6
+ export namespace LangSelector {
7
+ interface Lang {
8
+ code: string
9
+ title: string
10
+ path: string
11
+ }
12
+ // Tailwind 3.4.5 or higher is required to use anchor-name.
13
+ // https://github.com/tailwindlabs/tailwindcss/issues/13818
14
+ export interface Props extends React.ComponentPropsWithRef<'button'> {
15
+ current: string
16
+ langs: Lang[]
17
+ }
18
+ }
19
+
20
+ export const LangSelector = ({
21
+ current,
22
+ langs,
23
+ className,
24
+ ...restProps
25
+ }: LangSelector.Props) => {
26
+ const styleRoot = `
27
+ relative
28
+ group
29
+ `
30
+
31
+ const styleAnchorName = `
32
+ [anchor-name:--target]
33
+ `
34
+
35
+ // height: text-base(16px)*1.375 + p-3(12px)*2 + border(1px)*2 = 48px
36
+ const styleButton = `
37
+ w-fit
38
+ py-[1px]
39
+ sm:py-3
40
+ text-base
41
+ leading-snug
42
+ ${Style.clickable}
43
+ ${Style.focusRoundedWithBg}
44
+ ${Style.hoverUnderline}
45
+ ${styleAnchorName}
46
+ flex items-center
47
+ `
48
+
49
+ const stylePositionBottomRight = `
50
+ absolute
51
+ top-[anchor(--target_bottom)]
52
+ right-[anchor(--target_right)]
53
+ position-try-options:flip-block]
54
+ inset-[unset]
55
+ `
56
+
57
+ const targetId = React.useId()
58
+
59
+ const menuRef = React.useRef<HTMLUListElement>(null)
60
+
61
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
62
+ if (e.key === 'Tab') {
63
+ e.preventDefault()
64
+
65
+ const focusables: HTMLElement[] = Array.from(
66
+ menuRef.current?.querySelectorAll(focusableSelector) || []
67
+ )
68
+ if (focusables.length === 0) return
69
+
70
+ const tabIndex = focusables.findIndex(
71
+ (el) => el === document.activeElement
72
+ )
73
+ // If there is no focused element, focus the first element
74
+ if (tabIndex === -1) {
75
+ focusables[0].focus()
76
+ return
77
+ }
78
+ const nextIndex = mod(tabIndex + (e.shiftKey ? -1 : 1), focusables.length)
79
+ focusables[nextIndex].focus()
80
+
81
+ return
82
+ }
83
+ }
84
+
85
+ React.useEffect(() => {
86
+ const handleToggle = () => {
87
+ const focusable: HTMLElement | null | undefined =
88
+ menuRef.current?.querySelector(focusableSelector)
89
+ if (!focusable) return
90
+ focusable.focus()
91
+ }
92
+
93
+ menuRef.current?.addEventListener('toggle', handleToggle)
94
+
95
+ return () => menuRef.current?.removeEventListener('toggle', handleToggle)
96
+ }, [])
97
+
98
+ return (
99
+ <div className={cx(className, styleRoot)}>
100
+ <button
101
+ popoverTarget={targetId}
102
+ type="button"
103
+ className={styleButton}
104
+ {...restProps}
105
+ >
106
+ <Language size={20} className="hidden sm:mt-0.5 sm:inline-block" />
107
+ <span className="hidden sm:mx-1 sm:inline">Language</span>
108
+ <LanguageMobile size={44} className="inline-block sm:hidden" />
109
+ <KeyboadArrowDown
110
+ size={16}
111
+ className="inline-block sm:mt-1 group-[:has(:popover-open)]:rotate-180"
112
+ />
113
+ </button>
114
+ <PopoverMenu
115
+ ref={menuRef}
116
+ id={targetId}
117
+ popover="auto"
118
+ onKeyDown={handleKeyDown}
119
+ className={stylePositionBottomRight}
120
+ >
121
+ {langs.map((lang) => (
122
+ <PopoverMenuItem
123
+ key={lang.code}
124
+ aria-current={current === lang.code}
125
+ href={lang.path}
126
+ >
127
+ {lang.title}
128
+ </PopoverMenuItem>
129
+ ))}
130
+ </PopoverMenu>
131
+ </div>
132
+ )
133
+ }
134
+
135
+ LangSelector.displayName = 'LangSelector'
@@ -1,79 +1,51 @@
1
- import { cx } from '../libs/cx'
2
- import type { ComponentWithAs } from '../types/component'
3
- import { forwardRef } from '../libs/forward-ref'
1
+ import React from 'react'
2
+ import { cx, Style } from '@sakura-ui/helper'
3
+ import { Icon } from './Icon'
4
4
 
5
- // biome-ignore lint/suspicious/noEmptyInterface:
6
- export interface LinkProps {}
7
-
8
- export const Link: ComponentWithAs<'a', LinkProps> = forwardRef(
9
- (props, ref) => {
10
- const {
11
- as: Component = 'a',
12
- href,
13
- className,
14
- children,
15
- ...restProps
16
- } = props
17
-
18
- const styleUnderLine = `
19
- underline
20
- underline-offset-[0.125em]
21
- `
5
+ export namespace Link {
6
+ export interface Props<T extends React.ElementType> {
7
+ as?: T
8
+ }
9
+ }
22
10
 
23
- // Make lines break at each word
24
- const style = `
25
- rounded-sm
26
- cursor-pointer
27
- text-sea-600
28
- hover:text-sea-800
29
- active:text-sea-800
30
- visited:text-sea-900
31
- focus:outline
32
- focus:outline-2
33
- focus:outline-wood-500
34
- disabled:border-sumi-500
35
- [overflow-wrap:anywhere]
36
- `
11
+ export const Link = <T extends React.ElementType = 'a'>(
12
+ props: Link.Props<T> & Omit<React.ComponentProps<T>, keyof Link.Props<T>>
13
+ ) => {
14
+ const { as: Component = 'a', className, children, ...restProps } = props
37
15
 
38
- const styleIcon = `
39
- align-middle
40
- ml-0.5
41
- text-sumi-700
42
- font-icon
43
- font-light
44
- leading-4
45
- antialiased
46
- `
16
+ // Make lines break at each word
17
+ const style = `
18
+ cursor-pointer
19
+ text-blue-1000
20
+ active:text-orange-700
21
+ visited:text-magenta-900
22
+ ${Style.focusRoundedWithBg}
23
+ ${Style.underline}
24
+ disabled:border-solid-gray-500
25
+ [overflow-wrap:anywhere]
26
+ `
47
27
 
48
- if (href?.startsWith('http')) {
49
- return (
50
- <Component
51
- className={cx(style, className)}
52
- href={href}
53
- target="_blank"
54
- rel="noopener noreferrer"
55
- {...restProps}
56
- ref={ref}
57
- >
58
- <span className={styleUnderLine}>{children}</span>
59
- <span className={cx(styleIcon)} aria-hidden="true">
60
- open_in_new
61
- </span>
62
- <span className="sr-only">Opens in new tab</span>
63
- </Component>
64
- )
65
- }
28
+ if (restProps.href?.startsWith('http') || restProps.target === '_blank') {
66
29
  return (
67
30
  <Component
68
- className={cx(style, styleUnderLine, className)}
69
- href={href}
31
+ className={cx(style, className)}
32
+ target="_blank"
33
+ rel="noopener noreferrer"
70
34
  {...restProps}
71
- ref={ref}
72
35
  >
73
- {children}
36
+ <span>{children}</span>
37
+ <Icon opticalSize={16} altText="Opens in new tab" className="ml-0.5">
38
+ open_in_new
39
+ </Icon>
74
40
  </Component>
75
41
  )
76
42
  }
77
- )
43
+
44
+ return (
45
+ <Component className={cx(style, className)} {...restProps}>
46
+ {children}
47
+ </Component>
48
+ )
49
+ }
78
50
 
79
51
  Link.displayName = 'Link'
@@ -0,0 +1,91 @@
1
+ import React from 'react'
2
+ import { cx, Style } from '@sakura-ui/helper'
3
+ import { Card, CardHeader } from './Card'
4
+ import { Icon } from './Icon'
5
+
6
+ interface LinkContextType {
7
+ href: string
8
+ }
9
+ const LinkContext = React.createContext<LinkContextType>({ href: '' })
10
+
11
+ export namespace LinkCard {
12
+ export interface Props extends React.ComponentProps<'article'> {
13
+ href: string
14
+ }
15
+ }
16
+
17
+ export const LinkCard = ({
18
+ href,
19
+ className,
20
+ children,
21
+ ...rest
22
+ }: LinkCard.Props) => {
23
+ const styleLink = `
24
+ grid
25
+ outline-offset-4
26
+ rounded-2xl
27
+ sm:rounded-3xl
28
+ w-full h-full
29
+ ${Style.focusCard}
30
+ `
31
+
32
+ const styleHover = `
33
+ hover:text-blue-1000
34
+ hover:border-blue-1000
35
+ `
36
+
37
+ const isExternal = href?.startsWith('https://')
38
+
39
+ return (
40
+ <LinkContext.Provider value={{ href: href }}>
41
+ <a
42
+ className={cx(styleLink)}
43
+ href={href}
44
+ target={isExternal ? '_blank' : ''}
45
+ >
46
+ <Card className={cx(styleHover, className)} {...rest}>
47
+ {children}
48
+ </Card>
49
+ </a>
50
+ </LinkContext.Provider>
51
+ )
52
+ }
53
+
54
+ export interface LinkCardHeaderProps extends React.ComponentProps<'div'> {}
55
+
56
+ export const LinkCardHeader = ({
57
+ className,
58
+ children
59
+ }: LinkCardHeaderProps) => {
60
+ const { href } = React.useContext(LinkContext)
61
+
62
+ const styleHeading = `
63
+ flex
64
+ justify-between
65
+ items-center
66
+ `
67
+
68
+ const isExternal = href.startsWith('https://')
69
+
70
+ return (
71
+ <CardHeader className={cx(className, styleHeading)}>
72
+ {href === '' ? (
73
+ children
74
+ ) : (
75
+ <>
76
+ <span>
77
+ {children}
78
+ {isExternal ? (
79
+ <Icon opticalSize={16} className="ml-1">
80
+ open_in_new
81
+ </Icon>
82
+ ) : (
83
+ ''
84
+ )}
85
+ </span>
86
+ {isExternal ? '' : <Icon opticalSize={16}>arrow_forward</Icon>}
87
+ </>
88
+ )}
89
+ </CardHeader>
90
+ )
91
+ }
@@ -1,17 +1,18 @@
1
1
  import React from 'react'
2
- import { cx } from '../libs/cx'
2
+ import { cx } from '@sakura-ui/helper'
3
3
 
4
- export interface UlProps extends React.ComponentPropsWithoutRef<'ul'> {}
4
+ export namespace Ul {
5
+ export interface Props extends React.ComponentPropsWithoutRef<'ul'> {}
6
+ }
5
7
 
6
- export const Ul = (props: UlProps) => {
8
+ export const Ul = (props: Ul.Props) => {
7
9
  const { className, children, ...restProps } = props
8
10
 
9
11
  const style = `
10
12
  list-disc
11
- list-inside
12
- -indent-4
13
- pl-4
14
- marker:text-base-sm
13
+ [&_&]:list-circle
14
+ [&_&_&]:list-square
15
+ pl-8
15
16
  `
16
17
  return (
17
18
  <ul className={cx(style, className)} {...restProps}>
@@ -20,16 +21,17 @@ export const Ul = (props: UlProps) => {
20
21
  )
21
22
  }
22
23
 
23
- export interface OlProps extends React.ComponentPropsWithoutRef<'ol'> {}
24
+ export namespace Ol {
25
+ export interface Props extends React.ComponentPropsWithoutRef<'ol'> {}
26
+ }
24
27
 
25
- export const Ol = (props: OlProps) => {
28
+ export const Ol = (props: Ol.Props) => {
26
29
  const { className, children, ...restProps } = props
27
30
 
28
31
  const style = `
29
32
  list-decimal
30
- list-inside
31
- -indent-4
32
- pl-4
33
+ [&_&]:list-lower-latin
34
+ pl-8
33
35
  `
34
36
  return (
35
37
  <ol className={cx(style, className)} {...restProps}>
@@ -0,0 +1,112 @@
1
+ import React from 'react'
2
+ import { cx, mod, focusableSelector, Style } from '@sakura-ui/helper'
3
+ import { Menu, MenuMobile, Close, CloseMobile } from '@/icons'
4
+
5
+ export namespace MenuButton {
6
+ export interface Props extends React.ComponentPropsWithRef<'button'> {}
7
+ }
8
+
9
+ export const MenuButton = ({
10
+ className,
11
+ children,
12
+ onChange,
13
+ ...rest
14
+ }: MenuButton.Props) => {
15
+ // leading 16px * 1.375(snug) + padding top 12px + padding bottom 12px + border * 2 = 48px
16
+ const styleMenuItem = `
17
+ w-fit
18
+ py-[1px]
19
+ sm:py-3
20
+ text-base
21
+ leading-snug
22
+ ${Style.clickable}
23
+ ${Style.hoverUnderline}
24
+ ${Style.focusRoundedWithBg}
25
+ `
26
+
27
+ const dialogRef = React.useRef<HTMLDialogElement>(null)
28
+
29
+ React.useEffect(() => {
30
+ document.body.style.overflow = 'auto'
31
+ }, [])
32
+
33
+ const open = () => {
34
+ dialogRef.current?.showModal()
35
+ document.body.style.overflow = 'hidden'
36
+ }
37
+ const close = () => {
38
+ dialogRef.current?.close()
39
+ }
40
+
41
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
42
+ // Focus trap
43
+ if (e.key === 'Tab') {
44
+ e.preventDefault()
45
+
46
+ const focusables: HTMLElement[] = Array.from(
47
+ dialogRef.current?.querySelectorAll(focusableSelector) || []
48
+ )
49
+ if (focusables.length === 0) return
50
+
51
+ const tabIndex = focusables.findIndex(
52
+ (el) => el === document.activeElement
53
+ )
54
+ // If there is no focused element, focus the first element
55
+ if (tabIndex === -1) {
56
+ focusables[0].focus()
57
+ return
58
+ }
59
+ const nextIndex = mod(tabIndex + (e.shiftKey ? -1 : 1), focusables.length)
60
+ focusables[nextIndex].focus()
61
+
62
+ return
63
+ }
64
+ if (e.key === 'Escape') {
65
+ e.preventDefault()
66
+
67
+ close()
68
+ }
69
+ }
70
+
71
+ const handleOnClose = (_: React.KeyboardEvent<HTMLDialogElement>) => {
72
+ document.body.style.overflow = 'auto'
73
+ }
74
+
75
+ const handleOnClick = (e: React.MouseEvent<HTMLElement>) => {
76
+ if ((e.target as HTMLElement).nodeName === 'DIALOG') {
77
+ close()
78
+ }
79
+ }
80
+
81
+ return (
82
+ <div className={className}>
83
+ <dialog
84
+ ref={dialogRef}
85
+ className="peer"
86
+ onKeyDown={handleKeyDown}
87
+ onClick={handleOnClick}
88
+ onClose={handleOnClose}
89
+ >
90
+ {children}
91
+ </dialog>
92
+ <button
93
+ className={cx(styleMenuItem, 'flex items-center peer-open:hidden')}
94
+ onClick={open}
95
+ {...rest}
96
+ >
97
+ <MenuMobile size={44} className="inline-block sm:hidden" />
98
+ <Menu size={22} className="hidden sm:inline-block mr-1" />
99
+ <span className="hidden sm:inline">Menu</span>
100
+ </button>
101
+ <button
102
+ className={cx(styleMenuItem, 'peer-open:flex items-center hidden')}
103
+ onClick={close}
104
+ {...rest}
105
+ >
106
+ <CloseMobile size={44} className="inline-block sm:hidden" />
107
+ <Close size={22} className="hidden sm:inline-block mr-1" />
108
+ <span className="hidden sm:inline">Close</span>
109
+ </button>
110
+ </div>
111
+ )
112
+ }
@@ -0,0 +1,41 @@
1
+ import React, { type ComponentProps } from 'react'
2
+ import { cx, Style } from '@sakura-ui/helper'
3
+
4
+ export namespace NavigationItem {
5
+ export interface Props<T extends React.ElementType> {
6
+ as?: T
7
+ }
8
+ }
9
+
10
+ export const NavigationItem = <T extends React.ElementType = 'a'>(
11
+ props: NavigationItem.Props<T> &
12
+ Omit<React.ComponentProps<T>, keyof NavigationItem.Props<T>>
13
+ ) => {
14
+ const { as, className, children, ...rest } = props
15
+
16
+ const Component = as || 'a'
17
+
18
+ // leading 16px * 1.375(snug) + padding top 12px + padding bottom 12px + border * 2 = 48px
19
+ const style = `
20
+ inline-block
21
+ w-fit
22
+ py-3
23
+ text-base
24
+ leading-snug
25
+ ${Style.clickable}
26
+ ${Style.hoverUnderline}
27
+ ${Style.focusRectWithBg}
28
+ `
29
+
30
+ return (
31
+ <Component
32
+ className={cx(style, className)}
33
+ rel="noopener noreferrer"
34
+ {...rest}
35
+ >
36
+ {children}
37
+ </Component>
38
+ )
39
+ }
40
+
41
+ NavigationItem.displayName = 'NavigationItem'
@@ -0,0 +1,24 @@
1
+ import React from 'react'
2
+ import { cx } from '@sakura-ui/helper'
3
+
4
+ export namespace OverflowContainer {
5
+ export interface Props extends React.ComponentPropsWithoutRef<'div'> {}
6
+ }
7
+
8
+ export const OverflowContainer = (props: OverflowContainer.Props) => {
9
+ const { className, children, ...restProps } = props
10
+
11
+ const style = `
12
+ w-full
13
+ overflow-x-auto
14
+ whitespace-nowrap
15
+ print:overflow-visible
16
+ print:whitespace-normal
17
+ `
18
+
19
+ return (
20
+ <div className={cx(style, className)} {...restProps}>
21
+ {children}
22
+ </div>
23
+ )
24
+ }