廓形仪rn版本-技术调研
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
12 KiB

  1. 'use client';
  2. import React from 'react';
  3. import { createButton } from '@gluestack-ui/button';
  4. import { tva } from '@gluestack-ui/nativewind-utils/tva';
  5. import {
  6. withStyleContext,
  7. useStyleContext,
  8. } from '@gluestack-ui/nativewind-utils/withStyleContext';
  9. import { cssInterop } from 'nativewind';
  10. import { ActivityIndicator, Pressable, Text, View } from 'react-native';
  11. import type { VariantProps } from '@gluestack-ui/nativewind-utils';
  12. import { PrimitiveIcon, UIIcon } from '@gluestack-ui/icon';
  13. const SCOPE = 'BUTTON';
  14. const Root = withStyleContext(Pressable, SCOPE);
  15. const UIButton = createButton({
  16. Root: Root,
  17. Text,
  18. Group: View,
  19. Spinner: ActivityIndicator,
  20. Icon: UIIcon,
  21. });
  22. cssInterop(PrimitiveIcon, {
  23. className: {
  24. target: 'style',
  25. nativeStyleToProp: {
  26. height: true,
  27. width: true,
  28. fill: true,
  29. color: 'classNameColor',
  30. stroke: true,
  31. },
  32. },
  33. });
  34. const buttonStyle = tva({
  35. base: 'group/button rounded bg-primary-500 flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2',
  36. variants: {
  37. action: {
  38. primary:
  39. 'bg-primary-500 data-[hover=true]:bg-primary-600 data-[active=true]:bg-primary-700 border-primary-300 data-[hover=true]:border-primary-400 data-[active=true]:border-primary-500 data-[focus-visible=true]:web:ring-indicator-info',
  40. secondary:
  41. 'bg-secondary-500 border-secondary-300 data-[hover=true]:bg-secondary-600 data-[hover=true]:border-secondary-400 data-[active=true]:bg-secondary-700 data-[active=true]:border-secondary-700 data-[focus-visible=true]:web:ring-indicator-info',
  42. positive:
  43. 'bg-success-500 border-success-300 data-[hover=true]:bg-success-600 data-[hover=true]:border-success-400 data-[active=true]:bg-success-700 data-[active=true]:border-success-500 data-[focus-visible=true]:web:ring-indicator-info',
  44. negative:
  45. 'bg-error-500 border-error-300 data-[hover=true]:bg-error-600 data-[hover=true]:border-error-400 data-[active=true]:bg-error-700 data-[active=true]:border-error-500 data-[focus-visible=true]:web:ring-indicator-info',
  46. default:
  47. 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
  48. },
  49. variant: {
  50. link: 'px-0',
  51. outline:
  52. 'bg-transparent border data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
  53. solid: '',
  54. },
  55. size: {
  56. xs: 'px-3.5 h-8',
  57. sm: 'px-4 h-9',
  58. md: 'px-5 h-10',
  59. lg: 'px-6 h-11',
  60. xl: 'px-7 h-12',
  61. },
  62. },
  63. compoundVariants: [
  64. {
  65. action: 'primary',
  66. variant: 'link',
  67. class:
  68. 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
  69. },
  70. {
  71. action: 'secondary',
  72. variant: 'link',
  73. class:
  74. 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
  75. },
  76. {
  77. action: 'positive',
  78. variant: 'link',
  79. class:
  80. 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
  81. },
  82. {
  83. action: 'negative',
  84. variant: 'link',
  85. class:
  86. 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
  87. },
  88. {
  89. action: 'primary',
  90. variant: 'outline',
  91. class:
  92. 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
  93. },
  94. {
  95. action: 'secondary',
  96. variant: 'outline',
  97. class:
  98. 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
  99. },
  100. {
  101. action: 'positive',
  102. variant: 'outline',
  103. class:
  104. 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
  105. },
  106. {
  107. action: 'negative',
  108. variant: 'outline',
  109. class:
  110. 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
  111. },
  112. ],
  113. });
  114. const buttonTextStyle = tva({
  115. base: 'text-typography-0 font-semibold web:select-none',
  116. parentVariants: {
  117. action: {
  118. primary:
  119. 'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
  120. secondary:
  121. 'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
  122. positive:
  123. 'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
  124. negative:
  125. 'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
  126. },
  127. variant: {
  128. link: 'data-[hover=true]:underline data-[active=true]:underline',
  129. outline: '',
  130. solid:
  131. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  132. },
  133. size: {
  134. xs: 'text-xs',
  135. sm: 'text-sm',
  136. md: 'text-base',
  137. lg: 'text-lg',
  138. xl: 'text-xl',
  139. },
  140. },
  141. parentCompoundVariants: [
  142. {
  143. variant: 'solid',
  144. action: 'primary',
  145. class:
  146. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  147. },
  148. {
  149. variant: 'solid',
  150. action: 'secondary',
  151. class:
  152. 'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
  153. },
  154. {
  155. variant: 'solid',
  156. action: 'positive',
  157. class:
  158. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  159. },
  160. {
  161. variant: 'solid',
  162. action: 'negative',
  163. class:
  164. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  165. },
  166. {
  167. variant: 'outline',
  168. action: 'primary',
  169. class:
  170. 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
  171. },
  172. {
  173. variant: 'outline',
  174. action: 'secondary',
  175. class:
  176. 'text-typography-500 data-[hover=true]:text-primary-600 data-[active=true]:text-typography-700',
  177. },
  178. {
  179. variant: 'outline',
  180. action: 'positive',
  181. class:
  182. 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
  183. },
  184. {
  185. variant: 'outline',
  186. action: 'negative',
  187. class:
  188. 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
  189. },
  190. ],
  191. });
  192. const buttonIconStyle = tva({
  193. base: 'fill-none',
  194. parentVariants: {
  195. variant: {
  196. link: 'data-[hover=true]:underline data-[active=true]:underline',
  197. outline: '',
  198. solid:
  199. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  200. },
  201. size: {
  202. xs: 'h-3.5 w-3.5',
  203. sm: 'h-4 w-4',
  204. md: 'h-[18px] w-[18px]',
  205. lg: 'h-[18px] w-[18px]',
  206. xl: 'h-5 w-5',
  207. },
  208. action: {
  209. primary:
  210. 'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
  211. secondary:
  212. 'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
  213. positive:
  214. 'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
  215. negative:
  216. 'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
  217. },
  218. },
  219. parentCompoundVariants: [
  220. {
  221. variant: 'solid',
  222. action: 'primary',
  223. class:
  224. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  225. },
  226. {
  227. variant: 'solid',
  228. action: 'secondary',
  229. class:
  230. 'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
  231. },
  232. {
  233. variant: 'solid',
  234. action: 'positive',
  235. class:
  236. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  237. },
  238. {
  239. variant: 'solid',
  240. action: 'negative',
  241. class:
  242. 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
  243. },
  244. ],
  245. });
  246. const buttonGroupStyle = tva({
  247. base: '',
  248. variants: {
  249. space: {
  250. 'xs': 'gap-1',
  251. 'sm': 'gap-2',
  252. 'md': 'gap-3',
  253. 'lg': 'gap-4',
  254. 'xl': 'gap-5',
  255. '2xl': 'gap-6',
  256. '3xl': 'gap-7',
  257. '4xl': 'gap-8',
  258. },
  259. isAttached: {
  260. true: 'gap-0',
  261. },
  262. flexDirection: {
  263. 'row': 'flex-row',
  264. 'column': 'flex-col',
  265. 'row-reverse': 'flex-row-reverse',
  266. 'column-reverse': 'flex-col-reverse',
  267. },
  268. },
  269. });
  270. type IButtonProps = Omit<
  271. React.ComponentPropsWithoutRef<typeof UIButton>,
  272. 'context'
  273. > &
  274. VariantProps<typeof buttonStyle> & { className?: string };
  275. const Button = React.forwardRef<
  276. React.ComponentRef<typeof UIButton>,
  277. IButtonProps
  278. >(function Button(
  279. { className, variant = 'solid', size = 'md', action = 'primary', ...props },
  280. ref
  281. ) {
  282. return (
  283. <UIButton
  284. ref={ref}
  285. {...props}
  286. className={buttonStyle({ variant, size, action, class: className })}
  287. context={{ variant, size, action }}
  288. />
  289. );
  290. });
  291. type IButtonTextProps = React.ComponentPropsWithoutRef<typeof UIButton.Text> &
  292. VariantProps<typeof buttonTextStyle> & { className?: string };
  293. const ButtonText = React.forwardRef<
  294. React.ComponentRef<typeof UIButton.Text>,
  295. IButtonTextProps
  296. >(function ButtonText({ className, variant, size, action, ...props }, ref) {
  297. const {
  298. variant: parentVariant,
  299. size: parentSize,
  300. action: parentAction,
  301. } = useStyleContext(SCOPE);
  302. return (
  303. <UIButton.Text
  304. ref={ref}
  305. {...props}
  306. className={buttonTextStyle({
  307. parentVariants: {
  308. variant: parentVariant,
  309. size: parentSize,
  310. action: parentAction,
  311. },
  312. variant,
  313. size,
  314. action,
  315. class: className,
  316. })}
  317. />
  318. );
  319. });
  320. const ButtonSpinner = UIButton.Spinner;
  321. type IButtonIcon = React.ComponentPropsWithoutRef<typeof UIButton.Icon> &
  322. VariantProps<typeof buttonIconStyle> & {
  323. className?: string | undefined;
  324. as?: React.ElementType;
  325. height?: number;
  326. width?: number;
  327. };
  328. const ButtonIcon = React.forwardRef<
  329. React.ComponentRef<typeof UIButton.Icon>,
  330. IButtonIcon
  331. >(function ButtonIcon({ className, size, ...props }, ref) {
  332. const {
  333. variant: parentVariant,
  334. size: parentSize,
  335. action: parentAction,
  336. } = useStyleContext(SCOPE);
  337. if (typeof size === 'number') {
  338. return (
  339. <UIButton.Icon
  340. ref={ref}
  341. {...props}
  342. className={buttonIconStyle({ class: className })}
  343. size={size}
  344. />
  345. );
  346. } else if (
  347. (props.height !== undefined || props.width !== undefined) &&
  348. size === undefined
  349. ) {
  350. return (
  351. <UIButton.Icon
  352. ref={ref}
  353. {...props}
  354. className={buttonIconStyle({ class: className })}
  355. />
  356. );
  357. }
  358. return (
  359. <UIButton.Icon
  360. {...props}
  361. className={buttonIconStyle({
  362. parentVariants: {
  363. size: parentSize,
  364. variant: parentVariant,
  365. action: parentAction,
  366. },
  367. size,
  368. class: className,
  369. })}
  370. ref={ref}
  371. />
  372. );
  373. });
  374. type IButtonGroupProps = React.ComponentPropsWithoutRef<typeof UIButton.Group> &
  375. VariantProps<typeof buttonGroupStyle>;
  376. const ButtonGroup = React.forwardRef<
  377. React.ComponentRef<typeof UIButton.Group>,
  378. IButtonGroupProps
  379. >(function ButtonGroup(
  380. {
  381. className,
  382. space = 'md',
  383. isAttached = false,
  384. flexDirection = 'column',
  385. ...props
  386. },
  387. ref
  388. ) {
  389. return (
  390. <UIButton.Group
  391. className={buttonGroupStyle({
  392. class: className,
  393. space,
  394. isAttached,
  395. flexDirection,
  396. })}
  397. {...props}
  398. ref={ref}
  399. />
  400. );
  401. });
  402. Button.displayName = 'Button';
  403. ButtonText.displayName = 'ButtonText';
  404. ButtonSpinner.displayName = 'ButtonSpinner';
  405. ButtonIcon.displayName = 'ButtonIcon';
  406. ButtonGroup.displayName = 'ButtonGroup';
  407. export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup };