antd-solid 0.0.8 → 0.0.10

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 (49) hide show
  1. package/package.json +4 -3
  2. package/src/Button.tsx +125 -0
  3. package/src/Collapse/index.tsx +86 -0
  4. package/src/ColorPicker.tsx +11 -0
  5. package/src/Compact.tsx +15 -0
  6. package/src/DatePicker.tsx +30 -0
  7. package/src/Empty/PRESENTED_IMAGE_SIMPLE.tsx +15 -0
  8. package/src/Empty/assets/EmptySvg.tsx +43 -0
  9. package/src/Empty/assets/SimpleEmptySvg.tsx +16 -0
  10. package/src/Empty/index.tsx +20 -0
  11. package/src/Image.tsx +29 -0
  12. package/src/Input.tsx +202 -0
  13. package/src/InputNumber.test.tsx +46 -0
  14. package/src/InputNumber.tsx +125 -0
  15. package/src/Modal.tsx +196 -0
  16. package/src/Popconfirm.tsx +75 -0
  17. package/src/Popover.tsx +30 -0
  18. package/src/Progress.tsx +4 -0
  19. package/src/Radio.tsx +132 -0
  20. package/src/Result.tsx +38 -0
  21. package/src/Segmented/index.tsx +95 -0
  22. package/src/Select.tsx +128 -0
  23. package/src/Skeleton.tsx +14 -0
  24. package/src/Spin.tsx +23 -0
  25. package/src/Switch.tsx +34 -0
  26. package/src/Table.tsx +53 -0
  27. package/src/Tabs.tsx +131 -0
  28. package/src/Timeline.tsx +33 -0
  29. package/src/Tooltip.tsx +340 -0
  30. package/src/Tree.tsx +246 -0
  31. package/src/Upload.tsx +10 -0
  32. package/src/form/Form.tsx +94 -0
  33. package/src/form/FormItem.tsx +139 -0
  34. package/src/form/context.ts +16 -0
  35. package/src/form/index.ts +13 -0
  36. package/src/hooks/createControllableValue.ts +68 -0
  37. package/src/hooks/createUpdateEffect.ts +16 -0
  38. package/src/hooks/index.ts +2 -0
  39. package/src/hooks/useClickAway.ts +18 -0
  40. package/src/hooks/useSize.ts +26 -0
  41. package/src/index.ts +44 -0
  42. package/src/types/index.ts +5 -0
  43. package/src/utils/EventEmitter.ts +15 -0
  44. package/src/utils/ReactToSolid.tsx +38 -0
  45. package/src/utils/SolidToReact.tsx +27 -0
  46. package/src/utils/array.ts +21 -0
  47. package/src/utils/component.tsx +85 -0
  48. package/src/utils/solid.ts +53 -0
  49. package/src/utils/zh_CN.ts +236 -0
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "antd-solid",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "",
5
- "main": "es/index.js",
5
+ "main": "src/index.ts",
6
6
  "types": "es/index.d.ts",
7
7
  "type": "module",
8
8
  "devDependencies": {
@@ -68,7 +68,8 @@
68
68
  "files": [
69
69
  "dist",
70
70
  "es",
71
- "css"
71
+ "css",
72
+ "src"
72
73
  ],
73
74
  "scripts": {
74
75
  "docs:dev": "vitepress dev docs",
package/src/Button.tsx ADDED
@@ -0,0 +1,125 @@
1
+ import {
2
+ type Component,
3
+ mergeProps,
4
+ type ParentProps,
5
+ type JSX,
6
+ Show,
7
+ createSignal,
8
+ createMemo,
9
+ } from 'solid-js'
10
+ import cs from 'classnames'
11
+
12
+ interface ButtonProps extends ParentProps, JSX.CustomAttributes<HTMLButtonElement> {
13
+ type?: 'default' | 'primary' | 'dashed' | 'text' | 'link'
14
+ onClick?: ((e: MouseEvent) => void) | ((e: MouseEvent) => Promise<unknown>)
15
+ /**
16
+ * 默认: middle
17
+ * plain: 没有多余的 padding 和高度等
18
+ */
19
+ size?: 'large' | 'middle' | 'small' | 'plain'
20
+ class?: string
21
+ style?: JSX.CSSProperties
22
+ loading?: boolean
23
+ /**
24
+ * 设置危险按钮
25
+ */
26
+ danger?: boolean
27
+ }
28
+
29
+ const sizeClassMap = {
30
+ large: 'ant-px-15px ant-py-6px ant-h-40px ant-rounded-8px',
31
+ middle: 'ant-px-15px ant-py-4px ant-h-32px ant-rounded-6px',
32
+ small: 'ant-px-7px ant-h-24px ant-rounded-4px',
33
+ plain: 'ant-p-0',
34
+ } as const
35
+
36
+ const typeClassMap = {
37
+ default: (danger: boolean) =>
38
+ cs(
39
+ 'ant-bg-white',
40
+ danger
41
+ ? 'ant-[border:1px_solid_var(--ant-color-error)] ant-text-[var(--ant-color-error)] hover:ant-[border-color:var(--light-error-color)] hover:ant-text-[var(--light-error-color)] active:ant-[border-color:var(--dark-error-color)] active:ant-text-[var(--dark-error-color)]'
42
+ : 'ant-[border:1px_solid_var(--ant-color-border)] ant-text-[var(--dark-color)] hover:ant-[border-color:var(--light-primary-color)] hover:ant-text-[var(--light-primary-color)] active:ant-[border-color:var(--dark-primary-color)] active:ant-text-[var(--dark-primary-color)]',
43
+ ),
44
+ primary: (danger: boolean) =>
45
+ cs(
46
+ 'ant-border-none ant-text-white',
47
+ danger
48
+ ? 'ant-bg-[var(--ant-color-error)] hover:ant-bg-[var(--light-error-color)] active:ant-bg-[var(--dark-error-color)]'
49
+ : 'ant-bg-[var(--primary-color)] hover:ant-bg-[var(--light-primary-color)] active:ant-bg-[var(--dark-primary-color)]',
50
+ ),
51
+ dashed: (danger: boolean) =>
52
+ cs(
53
+ ' ant-bg-white',
54
+ danger
55
+ ? 'ant-[border:1px_dashed_var(--ant-color-error)] ant-text-[var(--ant-color-error)] hover:ant-[border-color:var(--light-error-color)] hover:ant-text-[var(--light-error-color)] active:ant-[border-color:var(--dark-error-color)] active:ant-text-[var(--dark-error-color)]'
56
+ : 'ant-[border:1px_dashed_var(--ant-color-border)] ant-text-[var(--dark-color)] hover:ant-[border-color:var(--light-primary-color)] hover:ant-text-[var(--light-primary-color)] active:ant-[border-color:var(--dark-primary-color)] active:ant-text-[var(--dark-primary-color)]',
57
+ ),
58
+ text: (danger: boolean) =>
59
+ cs(
60
+ 'ant-border-none ant-bg-transparent',
61
+ danger
62
+ ? 'ant-text-[var(--ant-color-error)] hover:ant-bg-[var(--error-bg-color)] active:ant-bg-[var(--error-bg-color)]'
63
+ : 'ant-text-[var(--dark-color)] hover:ant-bg-[rgba(0,0,0,0.06)] active:ant-bg-[rgba(0,0,0,.15)]',
64
+ ),
65
+ link: (danger: boolean) =>
66
+ cs(
67
+ 'ant-border-none ant-bg-transparent',
68
+ danger
69
+ ? 'ant-text-[var(--ant-color-error)] hover:ant-text-[var(--light-error-color)] active:ant-text-[var(--dark-error-color)]'
70
+ : 'ant-text-[var(--primary-color)] hover:ant-text-[var(--light-primary-color)] active:ant-text-[var(--dark-primary-color)]',
71
+ ),
72
+ } as const
73
+
74
+ const Button: Component<ButtonProps> = props => {
75
+ const mergedProps = mergeProps({ type: 'default', size: 'middle' } as ButtonProps, props)
76
+ const [innerLoading, setLoading] = createSignal(false)
77
+ const loading = createMemo(() => props.loading ?? innerLoading())
78
+
79
+ return (
80
+ <button
81
+ ref={mergedProps.ref}
82
+ class={cs(
83
+ 'ant-relative ant-cursor-pointer',
84
+ mergedProps.class,
85
+ sizeClassMap[mergedProps.size!],
86
+ typeClassMap[mergedProps.type!](props.danger ?? false),
87
+ loading() && 'ant-opacity-65',
88
+ 'ant-[--color:--light-primary-color]'
89
+ )}
90
+ style={mergedProps.style}
91
+ onClick={e => {
92
+ const res = mergedProps.onClick?.(e)
93
+ if (res instanceof Promise) {
94
+ setLoading(true)
95
+ res.finally(() => setLoading(false))
96
+ }
97
+
98
+ if (
99
+ mergedProps.type === 'default' ||
100
+ mergedProps.type === 'primary' ||
101
+ mergedProps.type === 'dashed'
102
+ ) {
103
+ const div = document.createElement('div')
104
+ div.className = cs(
105
+ props.danger ? 'ant-[--color:var(--light-error-color)]' : 'ant-[--color:var(--light-primary-color)]',
106
+ 'ant-absolute ant-inset-0 ant-rounded-inherit ant-[background:radial-gradient(var(--color),rgba(0,0,0,0))] ant-z--1 ant-keyframes-button-border[inset:0px][inset:-6px] ant-[animation:button-border_ease-out_0.3s]',
107
+ )
108
+ const onAnimationEnd = () => {
109
+ div.remove()
110
+ div.removeEventListener('animationend', onAnimationEnd)
111
+ }
112
+ div.addEventListener('animationend', onAnimationEnd)
113
+ e.currentTarget.insertBefore(div, e.currentTarget.childNodes[0])
114
+ }
115
+ }}
116
+ >
117
+ <Show when={loading()}>
118
+ <span class="i-ant-design:loading ant-[vertical-align:-0.125em] keyframes-spin ant-[animation:spin_1s_linear_infinite] ant-mr-8px" />
119
+ </Show>
120
+ <span>{mergedProps.children}</span>
121
+ </button>
122
+ )
123
+ }
124
+
125
+ export default Button
@@ -0,0 +1,86 @@
1
+ import { type JSX, type JSXElement, type Component, For, Show } from 'solid-js'
2
+ import cs from 'classnames'
3
+ import { Transition } from 'solid-transition-group'
4
+ import { type Key } from '../types'
5
+ import createControllableValue from '../hooks/createControllableValue'
6
+
7
+ export interface CollapseItem {
8
+ key: Key
9
+ label: JSXElement
10
+ children: JSXElement
11
+ }
12
+
13
+ export interface CollapseProps {
14
+ class?: string
15
+ defaultActiveKey?: Key[]
16
+ activeKey?: Key[]
17
+ items: CollapseItem[]
18
+ style?: JSX.CSSProperties
19
+ }
20
+
21
+ const Collapse: Component<CollapseProps> = props => {
22
+ const [activeKey, setActiveKey] = createControllableValue<Key[]>(props, {
23
+ defaultValuePropName: 'defaultActiveKey',
24
+ valuePropName: 'activeKey',
25
+ defaultValue: [],
26
+ })
27
+
28
+ return (
29
+ <div
30
+ class={cs(
31
+ 'ant-rounded-[var(--ant-border-radius-lg)] ant-[border:1px_solid_var(--ant-color-border)] ant-border-b-0',
32
+ props.class,
33
+ )}
34
+ style={props.style}
35
+ >
36
+ <For each={props.items}>
37
+ {item => (
38
+ <div class="ant-[border-bottom:1px_solid_var(--ant-color-border)] first:ant-rounded-t-[var(--ant-border-radius-lg)] last:ant-rounded-b-[var(--ant-border-radius-lg)] ant-cursor-pointer">
39
+ <div
40
+ class="ant-bg-[var(--ant-collapse-header-bg)] ant-text-[var(--ant-color-text-heading)] ant-p-[var(--ant-collapse-header-padding)]"
41
+ onClick={() => {
42
+ setActiveKey(keys => {
43
+ if (keys.includes(item.key)) {
44
+ return keys.filter(key => key !== item.key)
45
+ }
46
+ return [...keys, item.key]
47
+ })
48
+ }}
49
+ >
50
+ <span
51
+ class={cs(
52
+ 'i-ant-design:right-outlined',
53
+ 'ant-mr-[var(--ant-margin-sm)] ant-duration-.3s',
54
+ activeKey().includes(item.key) && 'ant-rotate-[90deg]',
55
+ )}
56
+ />
57
+ {item.label}
58
+ </div>
59
+ <Transition
60
+ onEnter={(el, done) => {
61
+ el.animate([{ height: '0px' }, { height: `${el.scrollHeight}px` }], {
62
+ duration: 300,
63
+ }).finished.finally(done)
64
+ }}
65
+ onExit={(el, done) => {
66
+ el.animate([{ height: `${el.scrollHeight}px` }, { height: '0px' }], {
67
+ duration: 300,
68
+ }).finished.finally(done)
69
+ }}
70
+ >
71
+ <Show when={activeKey().includes(item.key)}>
72
+ <div class="ant-overflow-hidden">
73
+ <div class="ant-p-[var(--ant-padding-sm)] ant-[border-top:1px_solid_var(--ant-color-border)]">
74
+ {item.children}
75
+ </div>
76
+ </div>
77
+ </Show>
78
+ </Transition>
79
+ </div>
80
+ )}
81
+ </For>
82
+ </div>
83
+ )
84
+ }
85
+
86
+ export default Collapse
@@ -0,0 +1,11 @@
1
+ import { ColorPicker } from 'antd'
2
+ import { type Color } from 'antd/es/color-picker'
3
+ import { reactToSolidComponent } from './utils/component'
4
+
5
+ export interface ColorPickerProps {
6
+ defaultValue?: string | Color
7
+ value?: string | Color
8
+ onChange?: (value: Color, hex: string) => void
9
+ }
10
+
11
+ export default reactToSolidComponent(ColorPicker)
@@ -0,0 +1,15 @@
1
+ import { type ParentProps } from 'solid-js'
2
+
3
+ interface CompactProps extends ParentProps {}
4
+
5
+ function Compact(props: CompactProps) {
6
+ return <div class="ant-compact ant-flex">{props.children}</div>
7
+ }
8
+
9
+ Compact.compactItemClass = 'p[.ant-compact]:ant-ml--1px'
10
+ Compact.compactItemRounded0Class = 'p[.ant-compact>*]:ant-rounded-0'
11
+ Compact.compactItemRoundedLeftClass = 'p[.ant-compact>:first-child]:ant-rounded-l-6px'
12
+ Compact.compactItemRoundedRightClass = 'p[.ant-compact>:last-child]:ant-rounded-r-6px'
13
+ Compact.compactItemZIndexClass = 'p[.ant-compact>*]:focus:ant-z-10 p[.ant-compact>*]:focus-within:ant-z-10'
14
+
15
+ export default Compact
@@ -0,0 +1,30 @@
1
+ import { DatePicker as DatePickerAntd } from 'antd'
2
+ import { type DatePickerProps, type RangePickerProps } from 'antd/es/date-picker'
3
+ import { reactToSolidComponent, replaceClassName } from './utils/component'
4
+
5
+ const RangePicker = replaceClassName(
6
+ reactToSolidComponent<
7
+ RangePickerProps & {
8
+ dropdownClassName?: string | undefined
9
+ popupClassName?: string | undefined
10
+ rootClassName?: string | undefined
11
+ }
12
+ >(DatePickerAntd.RangePicker),
13
+ )
14
+
15
+ const _DatePicker = replaceClassName(
16
+ reactToSolidComponent<
17
+ DatePickerProps & {
18
+ status?: '' | 'error' | 'warning' | undefined
19
+ hashId?: string | undefined
20
+ popupClassName?: string | undefined
21
+ rootClassName?: string | undefined
22
+ }
23
+ >(DatePickerAntd),
24
+ )
25
+ const DatePicker = _DatePicker as typeof _DatePicker & {
26
+ RangePicker: typeof RangePicker
27
+ }
28
+ DatePicker.RangePicker = RangePicker
29
+
30
+ export default DatePicker
@@ -0,0 +1,15 @@
1
+ import { type Component } from 'solid-js'
2
+ import SimpleEmptySvg from './assets/SimpleEmptySvg'
3
+
4
+ const PRESENTED_IMAGE_SIMPLE: Component = () => {
5
+ return (
6
+ <div class='ant-my-[var(--ant-margin-xl)] ant-mx-[var(--ant-margin-xs)]'>
7
+ <div class="ant-mb-[var(--ant-margin-xs)] ant-flex ant-justify-center">
8
+ <SimpleEmptySvg />
9
+ </div>
10
+ <div class="ant-text-[var(--ant-color-text-disabled)] ant-text-center">暂无数据</div>
11
+ </div>
12
+ )
13
+ }
14
+
15
+ export default PRESENTED_IMAGE_SIMPLE
@@ -0,0 +1,43 @@
1
+ const EmptySvg = () => (
2
+ <svg width="184" height="100" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg">
3
+ <g fill="none" fill-rule="evenodd">
4
+ <g transform="translate(24 31.67)">
5
+ <ellipse
6
+ fill-opacity=".8"
7
+ fill="#F5F5F7"
8
+ cx="67.797"
9
+ cy="106.89"
10
+ rx="67.797"
11
+ ry="12.668"
12
+ />
13
+ <path
14
+ d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
15
+ fill="#AEB8C2"
16
+ />
17
+ <path
18
+ d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
19
+ fill="url(#linearGradient-1)"
20
+ transform="translate(13.56)"
21
+ />
22
+ <path
23
+ d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
24
+ fill="#F5F5F7"
25
+ />
26
+ <path
27
+ d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
28
+ fill="#DCE0E6"
29
+ />
30
+ </g>
31
+ <path
32
+ d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
33
+ fill="#DCE0E6"
34
+ />
35
+ <g transform="translate(149.65 15.383)" fill="#FFF">
36
+ <ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815" />
37
+ <path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z" />
38
+ </g>
39
+ </g>
40
+ </svg>
41
+ )
42
+
43
+ export default EmptySvg
@@ -0,0 +1,16 @@
1
+ const EmptySvg = () => (
2
+ <svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg">
3
+ <g transform="translate(0 1)" fill="none" fill-rule="evenodd">
4
+ <ellipse fill="#f5f5f5" cx="32" cy="33" rx="32" ry="7" />
5
+ <g fill-rule="nonzero" stroke="#d9d9d9">
6
+ <path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z" />
7
+ <path
8
+ d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
9
+ fill="#fafafa"
10
+ />
11
+ </g>
12
+ </g>
13
+ </svg>
14
+ )
15
+
16
+ export default EmptySvg
@@ -0,0 +1,20 @@
1
+ import { type Component } from 'solid-js'
2
+ import PRESENTED_IMAGE_SIMPLE from './PRESENTED_IMAGE_SIMPLE'
3
+ import EmptySvg from './assets/EmptySvg'
4
+
5
+ const Empty: Component & {
6
+ PRESENTED_IMAGE_SIMPLE: Component
7
+ } = () => {
8
+ return (
9
+ <div>
10
+ <div class='ant-mb-[var(--ant-margin-xs)] ant-flex ant-justify-center'>
11
+ <EmptySvg />
12
+ </div>
13
+ <div class='ant-text-[var(--ant-color-text)] ant-text-center'>暂无数据</div>
14
+ </div>
15
+ )
16
+ }
17
+
18
+ Empty.PRESENTED_IMAGE_SIMPLE = PRESENTED_IMAGE_SIMPLE
19
+
20
+ export default Empty
package/src/Image.tsx ADDED
@@ -0,0 +1,29 @@
1
+ import { Image as ImageAntd } from 'antd'
2
+ import { configProvider, reactToSolidComponent, replaceClassName } from './utils/component'
3
+ import { solidToReact } from './utils/solid'
4
+ import { type JSXElement, createMemo } from 'solid-js'
5
+ import { mapValues } from 'lodash-es'
6
+
7
+ const _Image = replaceClassName(
8
+ reactToSolidComponent(configProvider(ImageAntd), () => (<div class="ant-inline-flex" />) as any),
9
+ )
10
+
11
+ type ImageProps = Omit<Parameters<typeof _Image>[0], 'placeholder'> & {
12
+ placeholder?: JSXElement
13
+ }
14
+
15
+ function Image(_props: ImageProps) {
16
+ const props = createMemo(() =>
17
+ mapValues(_props, (value, key) => {
18
+ switch (key) {
19
+ case 'placeholder':
20
+ return solidToReact(value)
21
+ default:
22
+ return value
23
+ }
24
+ }),
25
+ )
26
+ return <_Image {...(props() as any)} />
27
+ }
28
+
29
+ export default Image
package/src/Input.tsx ADDED
@@ -0,0 +1,202 @@
1
+ import { isNil, omit } from 'lodash-es'
2
+ import { Show, createMemo, splitProps } from 'solid-js'
3
+ import type { JSX, JSXElement, Component } from 'solid-js'
4
+ import cs from 'classnames'
5
+ import createControllableValue from './hooks/createControllableValue'
6
+ import { Dynamic } from 'solid-js/web'
7
+ import Compact from './Compact'
8
+
9
+ type CommonInputProps<T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement> =
10
+ JSX.CustomAttributes<T> & {
11
+ textarea?: boolean
12
+ defaultValue?: string | undefined
13
+ value?: string | undefined
14
+ addonBefore?: JSXElement
15
+ addonAfter?: JSXElement
16
+ prefix?: JSXElement
17
+ suffix?: JSXElement
18
+ placeholder?: string
19
+ /**
20
+ * 仅供 InputNumber 使用
21
+ */
22
+ actions?: JSXElement
23
+ /**
24
+ * 设置校验状态
25
+ */
26
+ status?: 'error' | 'warning'
27
+ onChange?: JSX.InputEventHandler<T, InputEvent>
28
+ onPressEnter?: JSX.EventHandler<T, KeyboardEvent>
29
+ onKeyDown?: JSX.EventHandler<T, KeyboardEvent>
30
+ }
31
+
32
+ const statusClassDict = {
33
+ default: (disabled: boolean) =>
34
+ cs(
35
+ 'ant-[border:1px_solid_var(--ant-color-border)]',
36
+ !disabled &&
37
+ 'hover:ant-border-[var(--primary-color)] focus-within:ant-border-[var(--primary-color)] focus-within:ant-[box-shadow:0_0_0_2px_rgba(5,145,255,0.1)]',
38
+ ),
39
+ error: (disabled: boolean) =>
40
+ cs(
41
+ 'ant-[border:1px_solid_var(--ant-color-error)]',
42
+ !disabled &&
43
+ 'hover:ant-border-[var(--light-error-color)] focus-within:ant-[box-shadow:0_0_0_2px_rgba(255,38,5,.06)]',
44
+ ),
45
+ warning: (disabled: boolean) =>
46
+ cs(
47
+ 'ant-[border:1px_solid_var(--warning-color)]',
48
+ !disabled &&
49
+ 'hover:ant-border-[var(--color-warning-border-hover)] focus-within:ant-[box-shadow:0_0_0_2px_rgba(255,215,5,.1)]',
50
+ ),
51
+ }
52
+
53
+ export function CommonInput<T extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement>(
54
+ props: CommonInputProps<T> &
55
+ Omit<JSX.InputHTMLAttributes<T>, 'onChange' | 'onInput' | 'onKeyDown'>,
56
+ ) {
57
+ const [{ style, onChange, onPressEnter, onKeyDown }, inputProps] = splitProps(props, [
58
+ 'defaultValue',
59
+ 'value',
60
+ 'class',
61
+ 'addonBefore',
62
+ 'addonAfter',
63
+ 'suffix',
64
+ 'onChange',
65
+ 'onPressEnter',
66
+ 'onKeyDown',
67
+ 'actions',
68
+ 'style',
69
+ ])
70
+
71
+ const [_, controllableProps] = splitProps(props, ['onChange'])
72
+ const [value, setValue] = createControllableValue(controllableProps)
73
+
74
+ const inputWrapClass = createMemo(() =>
75
+ cs(
76
+ 'ant-px-11px ant-py-4px ant-rounded-6px',
77
+ !props.textarea && 'ant-h-32px',
78
+ props.addonBefore ? 'ant-rounded-l-0' : Compact.compactItemRoundedLeftClass,
79
+ props.addonAfter ? 'ant-rounded-r-0' : Compact.compactItemRoundedRightClass,
80
+ statusClassDict[props.status ?? 'default'](!!inputProps.disabled),
81
+ Compact.compactItemRounded0Class,
82
+ Compact.compactItemZIndexClass,
83
+ ),
84
+ )
85
+ const hasPrefixOrSuffix = createMemo(
86
+ () => !isNil(props.prefix) || !isNil(props.suffix) || !isNil(props.actions),
87
+ )
88
+ const inputJSX = (
89
+ <Dynamic<Component<JSX.InputHTMLAttributes<HTMLInputElement>>>
90
+ component={
91
+ (props.textarea ? 'textarea' : 'input') as unknown as Component<
92
+ JSX.InputHTMLAttributes<HTMLInputElement>
93
+ >
94
+ }
95
+ {...(inputProps as JSX.InputHTMLAttributes<HTMLInputElement>)}
96
+ class={cs(
97
+ 'ant-w-full ant-[outline:none] ant-text-14px',
98
+ !hasPrefixOrSuffix() && inputWrapClass(),
99
+ inputProps.disabled &&
100
+ 'ant-bg-[var(--ant-color-bg-container-disabled)] ant-cursor-not-allowed',
101
+ )}
102
+ value={value() ?? ''}
103
+ onInput={e => {
104
+ setValue(e.target.value)
105
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
106
+ onChange?.(e as any)
107
+
108
+ if (Object.keys(props).includes('value')) {
109
+ e.target.value = value()
110
+ }
111
+ }}
112
+ onKeyDown={e => {
113
+ if (e.key === 'Enter') {
114
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
115
+ onPressEnter?.(e as any)
116
+ }
117
+
118
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
119
+ onKeyDown?.(e as any)
120
+ }}
121
+ />
122
+ )
123
+
124
+ return (
125
+ <div class={cs('ant-flex ant-w-full', Compact.compactItemClass)} style={style}>
126
+ <Show when={props.addonBefore}>
127
+ <div
128
+ class={cs(
129
+ 'ant-shrink-0 ant-flex ant-justify-center ant-items-center ant-px-11px ant-bg-[rgba(0,0,0,.02)] ant-[border:1px_solid_var(--ant-color-border)] ant-border-r-0 ant-rounded-l-6px ant-text-14px',
130
+ Compact.compactItemRounded0Class,
131
+ Compact.compactItemRoundedLeftClass,
132
+ )}
133
+ >
134
+ {props.addonBefore}
135
+ </div>
136
+ </Show>
137
+
138
+ <Show when={hasPrefixOrSuffix()} fallback={inputJSX}>
139
+ <div
140
+ class={cs(
141
+ 'ant-flex ant-items-center ant-w-full ant-relative ant-[--input-after-display:none] hover:ant-[--input-after-display:block] p:hover-child[input]:ant-border-[var(--primary-color)]',
142
+ inputWrapClass(),
143
+ )}
144
+ >
145
+ <Show when={props.prefix}>
146
+ <div class="ant-mr-4px">{props.prefix}</div>
147
+ </Show>
148
+
149
+ {inputJSX}
150
+
151
+ <Show when={props.suffix}>
152
+ <div class="ant-ml-4px">{props.suffix}</div>
153
+ </Show>
154
+
155
+ <Show when={props.actions}>
156
+ <div class="ant-[display:var(--input-after-display)] ant-absolute ant-top-0 ant-bottom-0 ant-right-0 ant-h-[calc(100%-2px)] ant-translate-y-1px -ant-translate-x-1px">
157
+ {props.actions}
158
+ </div>
159
+ </Show>
160
+ </div>
161
+ </Show>
162
+
163
+ <Show when={props.addonAfter}>
164
+ <div
165
+ class={cs(
166
+ 'ant-shrink-0 ant-flex ant-justify-center ant-items-center ant-px-11px ant-bg-[rgba(0,0,0,.02)] ant-[border:1px_solid_var(--ant-color-border)] ant-border-l-0 ant-rounded-r-6px ant-text-14px',
167
+ Compact.compactItemRounded0Class,
168
+ Compact.compactItemRoundedRightClass,
169
+ )}
170
+ >
171
+ {props.addonAfter}
172
+ </div>
173
+ </Show>
174
+ </div>
175
+ )
176
+ }
177
+
178
+ export type InputProps = Omit<CommonInputProps, 'actions' | 'textarea'> &
179
+ Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onInput' | 'onKeyDown'>
180
+
181
+ export type TextAreaProps = Omit<
182
+ CommonInputProps<HTMLTextAreaElement>,
183
+ 'prefix' | 'suffix' | 'textarea'
184
+ > &
185
+ Omit<
186
+ JSX.TextareaHTMLAttributes<HTMLTextAreaElement>,
187
+ 'actions' | 'onChange' | 'onInput' | 'onKeyDown'
188
+ >
189
+
190
+ const Input: Component<InputProps> & {
191
+ TextArea: Component<TextAreaProps>
192
+ } = props => {
193
+ return <CommonInput {...omit(props, ['actions'])} />
194
+ }
195
+
196
+ Input.TextArea = props => {
197
+ return (
198
+ <CommonInput<HTMLTextAreaElement> textarea {...omit(props, ['prefix', 'suffix', 'actions'])} />
199
+ )
200
+ }
201
+
202
+ export default Input
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { fireEvent, render } from '@solidjs/testing-library'
3
+ import InputNumber from './InputNumber'
4
+ import '@testing-library/jest-dom'
5
+
6
+ describe('InputNumber component', () => {
7
+ it('onChange', () => {
8
+ const onChange = vi.fn()
9
+ const { getByPlaceholderText } = render(() => (
10
+ <InputNumber placeholder="input-number" onChange={onChange} />
11
+ ))
12
+
13
+ const input: HTMLInputElement = getByPlaceholderText('input-number')
14
+ input.value = '123'
15
+ fireEvent.input(input)
16
+ expect(onChange).toHaveBeenLastCalledWith(123)
17
+
18
+ input.value = '1234'
19
+ fireEvent.input(input)
20
+ expect(onChange).toBeCalledTimes(2)
21
+ expect(onChange).toHaveBeenLastCalledWith(1234)
22
+
23
+ input.value = '1234'
24
+ fireEvent.input(input)
25
+ expect(onChange).toBeCalledTimes(2)
26
+
27
+ input.value = '1234.'
28
+ fireEvent.input(input)
29
+ expect(onChange).toBeCalledTimes(2)
30
+
31
+ input.value = '1234.0'
32
+ fireEvent.input(input)
33
+ expect(onChange).toBeCalledTimes(2)
34
+
35
+ input.value = '1234.01'
36
+ fireEvent.input(input)
37
+ expect(onChange).toBeCalledTimes(3)
38
+
39
+ input.value = '123x'
40
+ fireEvent.input(input)
41
+ expect(onChange).toBeCalledTimes(3)
42
+
43
+ fireEvent.blur(input)
44
+ expect(onChange).toBeCalledTimes(3)
45
+ })
46
+ })