seven-design-ui 0.0.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 (157) hide show
  1. package/.eslintrc.json +35 -0
  2. package/.prettierrc.json +10 -0
  3. package/CONTRIBUTING.md +350 -0
  4. package/PROJECT_STRUCTURE.md +343 -0
  5. package/README.md +83 -0
  6. package/app/globals.css +125 -0
  7. package/app/layout.tsx +45 -0
  8. package/app/page.tsx +202 -0
  9. package/components/theme-provider.tsx +11 -0
  10. package/components/ui/accordion.tsx +66 -0
  11. package/components/ui/alert-dialog.tsx +157 -0
  12. package/components/ui/alert.tsx +66 -0
  13. package/components/ui/aspect-ratio.tsx +11 -0
  14. package/components/ui/avatar.tsx +53 -0
  15. package/components/ui/badge.tsx +46 -0
  16. package/components/ui/breadcrumb.tsx +109 -0
  17. package/components/ui/button-group.tsx +83 -0
  18. package/components/ui/button.tsx +60 -0
  19. package/components/ui/calendar.tsx +213 -0
  20. package/components/ui/card.tsx +92 -0
  21. package/components/ui/carousel.tsx +241 -0
  22. package/components/ui/chart.tsx +353 -0
  23. package/components/ui/checkbox.tsx +32 -0
  24. package/components/ui/collapsible.tsx +33 -0
  25. package/components/ui/command.tsx +184 -0
  26. package/components/ui/context-menu.tsx +252 -0
  27. package/components/ui/dialog.tsx +143 -0
  28. package/components/ui/drawer.tsx +135 -0
  29. package/components/ui/dropdown-menu.tsx +257 -0
  30. package/components/ui/empty.tsx +104 -0
  31. package/components/ui/field.tsx +244 -0
  32. package/components/ui/form.tsx +167 -0
  33. package/components/ui/hover-card.tsx +44 -0
  34. package/components/ui/input-group.tsx +169 -0
  35. package/components/ui/input-otp.tsx +77 -0
  36. package/components/ui/input.tsx +21 -0
  37. package/components/ui/item.tsx +193 -0
  38. package/components/ui/kbd.tsx +28 -0
  39. package/components/ui/label.tsx +24 -0
  40. package/components/ui/menubar.tsx +276 -0
  41. package/components/ui/navigation-menu.tsx +166 -0
  42. package/components/ui/pagination.tsx +127 -0
  43. package/components/ui/popover.tsx +48 -0
  44. package/components/ui/progress.tsx +31 -0
  45. package/components/ui/radio-group.tsx +45 -0
  46. package/components/ui/resizable.tsx +56 -0
  47. package/components/ui/scroll-area.tsx +58 -0
  48. package/components/ui/select.tsx +185 -0
  49. package/components/ui/separator.tsx +28 -0
  50. package/components/ui/sheet.tsx +139 -0
  51. package/components/ui/sidebar.tsx +726 -0
  52. package/components/ui/skeleton.tsx +13 -0
  53. package/components/ui/slider.tsx +63 -0
  54. package/components/ui/sonner.tsx +25 -0
  55. package/components/ui/spinner.tsx +16 -0
  56. package/components/ui/switch.tsx +31 -0
  57. package/components/ui/table.tsx +116 -0
  58. package/components/ui/tabs.tsx +66 -0
  59. package/components/ui/textarea.tsx +18 -0
  60. package/components/ui/toast.tsx +129 -0
  61. package/components/ui/toaster.tsx +35 -0
  62. package/components/ui/toggle-group.tsx +73 -0
  63. package/components/ui/toggle.tsx +47 -0
  64. package/components/ui/tooltip.tsx +61 -0
  65. package/components/ui/use-mobile.tsx +19 -0
  66. package/components/ui/use-toast.ts +191 -0
  67. package/components.json +21 -0
  68. package/docs/components/button.mdx +155 -0
  69. package/docs/components/input.mdx +157 -0
  70. package/docs/components/pagination.mdx +186 -0
  71. package/docs/components/switch.mdx +134 -0
  72. package/docs/doc_build/.nojekyll +0 -0
  73. package/docs/doc_build/404.html +15 -0
  74. package/docs/doc_build/components/button.html +39 -0
  75. package/docs/doc_build/components/input.html +39 -0
  76. package/docs/doc_build/components/pagination.html +39 -0
  77. package/docs/doc_build/components/switch.html +38 -0
  78. package/docs/doc_build/guide/introduction.html +58 -0
  79. package/docs/doc_build/guide/quick-start.html +98 -0
  80. package/docs/doc_build/guide/theme.html +139 -0
  81. package/docs/doc_build/index.html +15 -0
  82. package/docs/doc_build/logo.svg +1 -0
  83. package/docs/doc_build/static/css/styles.5a3e7113.css +1 -0
  84. package/docs/doc_build/static/js/414.04bb58dd.js +6 -0
  85. package/docs/doc_build/static/js/414.04bb58dd.js.LICENSE.txt +21 -0
  86. package/docs/doc_build/static/js/async/166.f43be01a.js +2 -0
  87. package/docs/doc_build/static/js/async/166.f43be01a.js.LICENSE.txt +19 -0
  88. package/docs/doc_build/static/js/async/218.cd780e24.js +1 -0
  89. package/docs/doc_build/static/js/async/232.11414fd7.js +1 -0
  90. package/docs/doc_build/static/js/async/416.b217df75.js +1 -0
  91. package/docs/doc_build/static/js/async/509.97958e51.js +1 -0
  92. package/docs/doc_build/static/js/async/512.9047d21e.js +1 -0
  93. package/docs/doc_build/static/js/async/531.131f5963.js +1 -0
  94. package/docs/doc_build/static/js/async/562.b402b94f.js +2 -0
  95. package/docs/doc_build/static/js/async/562.b402b94f.js.LICENSE.txt +11 -0
  96. package/docs/doc_build/static/js/async/637.cb5d76c9.js +1 -0
  97. package/docs/doc_build/static/js/async/712.558b85be.js +1 -0
  98. package/docs/doc_build/static/js/index.0991c749.js +1 -0
  99. package/docs/doc_build/static/js/lib-react.ce4199ca.js +2 -0
  100. package/docs/doc_build/static/js/lib-react.ce4199ca.js.LICENSE.txt +49 -0
  101. package/docs/doc_build/static/js/lib-router.4000fe55.js +2 -0
  102. package/docs/doc_build/static/js/lib-router.4000fe55.js.LICENSE.txt +32 -0
  103. package/docs/doc_build/static/js/styles.f2af9a40.js +1 -0
  104. package/docs/doc_build/static/search_index.72c9c372.json +1 -0
  105. package/docs/docs/public/logo.svg +1 -0
  106. package/docs/guide/introduction.md +50 -0
  107. package/docs/guide/quick-start.md +132 -0
  108. package/docs/guide/theme.md +230 -0
  109. package/docs/index.md +85 -0
  110. package/docs/package.json +22 -0
  111. package/docs/public/logo.svg +1 -0
  112. package/docs/rspress.config.ts +116 -0
  113. package/hooks/use-mobile.ts +19 -0
  114. package/hooks/use-toast.ts +191 -0
  115. package/next.config.mjs +11 -0
  116. package/package.json +75 -0
  117. package/packages/components/package.json +34 -0
  118. package/packages/components/src/button/Button.tsx +83 -0
  119. package/packages/components/src/button/button.css +256 -0
  120. package/packages/components/src/button/index.ts +2 -0
  121. package/packages/components/src/index.ts +8 -0
  122. package/packages/components/src/input/Input.tsx +230 -0
  123. package/packages/components/src/input/index.ts +2 -0
  124. package/packages/components/src/input/input.css +99 -0
  125. package/packages/components/src/pagination/Pagination.tsx +271 -0
  126. package/packages/components/src/pagination/index.ts +1 -0
  127. package/packages/components/src/pagination/pagination.css +225 -0
  128. package/packages/components/src/switch/Switch.tsx +145 -0
  129. package/packages/components/src/switch/index.ts +2 -0
  130. package/packages/components/src/switch/switch.css +119 -0
  131. package/packages/components/tsconfig.json +12 -0
  132. package/packages/components/vite.config.ts +31 -0
  133. package/packages/core/package.json +23 -0
  134. package/packages/core/src/hooks/useControllableState.ts +31 -0
  135. package/packages/core/src/hooks/useEventListener.ts +37 -0
  136. package/packages/core/src/index.ts +7 -0
  137. package/packages/core/src/utils/classnames.ts +48 -0
  138. package/packages/core/tsconfig.json +12 -0
  139. package/packages/core/vite.config.ts +24 -0
  140. package/packages/theme/package.json +20 -0
  141. package/packages/theme/src/index.css +138 -0
  142. package/packages/theme/src/index.ts +1 -0
  143. package/packages/theme/vite.config.ts +21 -0
  144. package/play/index.html +13 -0
  145. package/play/package.json +25 -0
  146. package/play/src/App.tsx +237 -0
  147. package/play/src/index.css +93 -0
  148. package/play/src/main.tsx +14 -0
  149. package/play/tsconfig.json +8 -0
  150. package/play/vite.config.ts +10 -0
  151. package/pnpm-workspace.yaml +4 -0
  152. package/postcss.config.mjs +8 -0
  153. package/public/logo.svg +1 -0
  154. package/scripts/build.sh +19 -0
  155. package/scripts/deploy-docs.js +38 -0
  156. package/styles/globals.css +125 -0
  157. package/tsconfig.json +30 -0
@@ -0,0 +1,225 @@
1
+ /* Pagination 组件样式 */
2
+ .sd-pagination {
3
+ display: flex;
4
+ align-items: center;
5
+ gap: 8px;
6
+ font-size: var(--sd-font-size-base);
7
+ color: var(--sd-text-color-regular);
8
+ user-select: none;
9
+ }
10
+
11
+ /* 大小样式 */
12
+ .sd-pagination--s {
13
+ font-size: var(--sd-font-size-small);
14
+ }
15
+
16
+ .sd-pagination--s .sd-pagination__item {
17
+ min-width: 28px;
18
+ height: 28px;
19
+ padding: 0 8px;
20
+ }
21
+
22
+ .sd-pagination--s .sd-pagination__size-select,
23
+ .sd-pagination--s .sd-pagination__jumper-input {
24
+ height: 28px;
25
+ font-size: var(--sd-font-size-small);
26
+ }
27
+
28
+ .sd-pagination--m .sd-pagination__item {
29
+ min-width: 32px;
30
+ height: 32px;
31
+ padding: 0 10px;
32
+ }
33
+
34
+ .sd-pagination--m .sd-pagination__size-select,
35
+ .sd-pagination--m .sd-pagination__jumper-input {
36
+ height: 32px;
37
+ }
38
+
39
+ /* 基础按钮样式 */
40
+ .sd-pagination__item {
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ min-width: 32px;
45
+ height: 32px;
46
+ padding: 0 10px;
47
+ font-size: inherit;
48
+ font-weight: 500;
49
+ line-height: 1;
50
+ white-space: nowrap;
51
+ cursor: pointer;
52
+ background-color: var(--sd-fill-color-blank);
53
+ border: 1px solid var(--sd-border-color);
54
+ border-radius: var(--sd-border-radius-base);
55
+ color: var(--sd-text-color-regular);
56
+ transition: all var(--sd-transition-duration-fast) var(--sd-transition-function-ease-in-out-bezier);
57
+ outline: none;
58
+ user-select: none;
59
+ }
60
+
61
+ .sd-pagination__item:hover,
62
+ .sd-pagination__item:focus {
63
+ color: var(--sd-color-primary);
64
+ border-color: var(--sd-color-primary-light-7);
65
+ background-color: var(--sd-color-primary-light-9);
66
+ }
67
+
68
+ .sd-pagination__item:active {
69
+ color: var(--sd-color-primary-dark-2);
70
+ border-color: var(--sd-color-primary-dark-2);
71
+ }
72
+
73
+ .sd-pagination__item.is-disabled {
74
+ color: var(--sd-text-color-disabled);
75
+ cursor: not-allowed;
76
+ background-color: var(--sd-fill-color-disabled);
77
+ border-color: var(--sd-border-color-light);
78
+ }
79
+
80
+ .sd-pagination__item.is-disabled:hover,
81
+ .sd-pagination__item.is-disabled:focus,
82
+ .sd-pagination__item.is-disabled:active {
83
+ color: var(--sd-text-color-disabled);
84
+ background-color: var(--sd-fill-color-disabled);
85
+ border-color: var(--sd-border-color-light);
86
+ }
87
+
88
+ /* 页码按钮 */
89
+ .sd-pagination__page.is-active {
90
+ color: var(--sd-color-white);
91
+ background-color: var(--sd-color-primary);
92
+ border-color: var(--sd-color-primary);
93
+ cursor: default;
94
+ }
95
+
96
+ .sd-pagination__page.is-active:hover,
97
+ .sd-pagination__page.is-active:focus,
98
+ .sd-pagination__page.is-active:active {
99
+ color: var(--sd-color-white);
100
+ background-color: var(--sd-color-primary);
101
+ border-color: var(--sd-color-primary);
102
+ }
103
+
104
+ /* 上一页/下一页按钮 */
105
+ .sd-pagination__prev,
106
+ .sd-pagination__next {
107
+ font-weight: 600;
108
+ }
109
+
110
+ /* 省略号样式 */
111
+ .sd-pagination__more {
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ min-width: 32px;
116
+ height: 32px;
117
+ padding: 0 4px;
118
+ color: var(--sd-text-color-regular);
119
+ cursor: default;
120
+ user-select: none;
121
+ }
122
+
123
+ /* 每页容量选择器 */
124
+ .sd-pagination__size-changer {
125
+ display: flex;
126
+ align-items: center;
127
+ margin-left: 12px;
128
+ }
129
+
130
+ .sd-pagination__size-select {
131
+ padding: 4px 8px;
132
+ font-size: inherit;
133
+ border: 1px solid var(--sd-border-color);
134
+ border-radius: var(--sd-border-radius-base);
135
+ background-color: var(--sd-fill-color-blank);
136
+ color: var(--sd-text-color-regular);
137
+ cursor: pointer;
138
+ outline: none;
139
+ transition: border-color var(--sd-transition-duration-fast) var(--sd-transition-function-ease-in-out-bezier);
140
+ }
141
+
142
+ .sd-pagination__size-select:hover,
143
+ .sd-pagination__size-select:focus {
144
+ border-color: var(--sd-color-primary);
145
+ }
146
+
147
+ .sd-pagination__size-select:focus {
148
+ outline: 2px solid var(--sd-color-primary-light-9);
149
+ outline-offset: -1px;
150
+ }
151
+
152
+ /* 跳转输入框 */
153
+ .sd-pagination__jumper {
154
+ display: flex;
155
+ align-items: center;
156
+ margin-left: 12px;
157
+ gap: 4px;
158
+ }
159
+
160
+ .sd-pagination__jumper-text {
161
+ color: var(--sd-text-color-regular);
162
+ font-size: inherit;
163
+ }
164
+
165
+ .sd-pagination__jumper-input {
166
+ width: 50px;
167
+ padding: 4px 8px;
168
+ font-size: inherit;
169
+ border: 1px solid var(--sd-border-color);
170
+ border-radius: var(--sd-border-radius-base);
171
+ background-color: var(--sd-fill-color-blank);
172
+ color: var(--sd-text-color-regular);
173
+ text-align: center;
174
+ outline: none;
175
+ transition: border-color var(--sd-transition-duration-fast) var(--sd-transition-function-ease-in-out-bezier);
176
+ }
177
+
178
+ .sd-pagination__jumper-input:hover,
179
+ .sd-pagination__jumper-input:focus {
180
+ border-color: var(--sd-color-primary);
181
+ }
182
+
183
+ .sd-pagination__jumper-input:focus {
184
+ outline: 2px solid var(--sd-color-primary-light-9);
185
+ outline-offset: -1px;
186
+ }
187
+
188
+ .sd-pagination__jumper-input::-webkit-outer-spin-button,
189
+ .sd-pagination__jumper-input::-webkit-inner-spin-button {
190
+ -webkit-appearance: none;
191
+ margin: 0;
192
+ }
193
+
194
+ .sd-pagination__jumper-input[type=number] {
195
+ -moz-appearance: textfield;
196
+ }
197
+
198
+ /* 总数信息 */
199
+ .sd-pagination__total {
200
+ margin-left: 12px;
201
+ color: var(--sd-text-color-secondary);
202
+ font-size: inherit;
203
+ white-space: nowrap;
204
+ }
205
+
206
+ /* 响应式设计 */
207
+ @media (max-width: 768px) {
208
+ .sd-pagination {
209
+ flex-wrap: wrap;
210
+ gap: 6px;
211
+ }
212
+
213
+ .sd-pagination__size-changer,
214
+ .sd-pagination__jumper,
215
+ .sd-pagination__total {
216
+ margin-left: 0;
217
+ margin-top: 8px;
218
+ }
219
+
220
+ .sd-pagination__item {
221
+ min-width: 28px;
222
+ height: 28px;
223
+ padding: 0 6px;
224
+ }
225
+ }
@@ -0,0 +1,145 @@
1
+ 'use client';
2
+
3
+ import React from "react"
4
+
5
+ import { forwardRef } from 'react'
6
+ import { classnames } from '@seven-design-ui/core'
7
+ import './switch.css'
8
+
9
+ export interface SwitchProps {
10
+ /** 是否选中 */
11
+ checked?: boolean
12
+ /** 默认是否选中 */
13
+ defaultChecked?: boolean
14
+ /** 是否禁用 */
15
+ disabled?: boolean
16
+ /** 是否加载中 */
17
+ loading?: boolean
18
+ /** 选中状态显示的内容 */
19
+ checkedNode?: React.ReactNode
20
+ /** 未选中状态显示的内容 */
21
+ unCheckedNode?: React.ReactNode
22
+ /** 选中状态的背景色 */
23
+ checkedColor?: string
24
+ /** 未选中状态的背景色 */
25
+ unCheckedColor?: string
26
+ /** 自定义类名 */
27
+ className?: string
28
+ /** 值改变时的回调 */
29
+ onChange?: (checked: boolean) => void
30
+ /** 原生 input 的 name 属性 */
31
+ name?: string
32
+ /** 原生 input 的 id 属性 */
33
+ id?: string
34
+ }
35
+
36
+ export const Switch = forwardRef<HTMLInputElement, SwitchProps>((props, ref) => {
37
+ const {
38
+ checked,
39
+ defaultChecked = false,
40
+ disabled = false,
41
+ loading = false,
42
+ checkedNode,
43
+ unCheckedNode,
44
+ checkedColor,
45
+ unCheckedColor,
46
+ className,
47
+ onChange,
48
+ name,
49
+ id,
50
+ } = props
51
+
52
+ // 内部状态管理 - 只在非受控模式下使用
53
+ const [internalChecked, setInternalChecked] = React.useState(defaultChecked)
54
+
55
+ // 判断是否为受控模式
56
+ const isControlled = checked !== undefined
57
+
58
+ // 当前的开关状态
59
+ const currentChecked = isControlled ? checked : internalChecked
60
+
61
+ const handleChange = () => {
62
+ if (disabled || loading) return
63
+
64
+ const newChecked = !currentChecked
65
+
66
+ // 如果是非受控模式,更新内部状态
67
+ if (!isControlled) {
68
+ setInternalChecked(newChecked)
69
+ }
70
+
71
+ // 调用onChange回调,传递boolean值
72
+ if (onChange) {
73
+ onChange(newChecked)
74
+ }
75
+ }
76
+
77
+ const handleKeyDown = (e: React.KeyboardEvent) => {
78
+ if (e.key === 'Enter' || e.key === ' ') {
79
+ e.preventDefault()
80
+ handleChange()
81
+ }
82
+ }
83
+
84
+ const classes = classnames(
85
+ 'sd-switch',
86
+ {
87
+ 'is-checked': currentChecked,
88
+ 'is-disabled': disabled || loading,
89
+ 'is-loading': loading,
90
+ },
91
+ className
92
+ )
93
+
94
+ // 计算背景色样式
95
+ const coreStyle: React.CSSProperties = {}
96
+ if (currentChecked && checkedColor) {
97
+ coreStyle.backgroundColor = checkedColor
98
+ } else if (!currentChecked && unCheckedColor) {
99
+ coreStyle.backgroundColor = unCheckedColor
100
+ }
101
+
102
+ return (
103
+ <div
104
+ className={classes}
105
+ role="switch"
106
+ aria-checked={currentChecked}
107
+ aria-disabled={disabled || loading}
108
+ tabIndex={disabled || loading ? -1 : 0}
109
+ onClick={handleChange}
110
+ onKeyDown={handleKeyDown}
111
+ >
112
+ <input
113
+ ref={ref}
114
+ type="checkbox"
115
+ className="sd-switch__input"
116
+ checked={currentChecked}
117
+ disabled={disabled || loading}
118
+ onChange={handleChange}
119
+ name={name}
120
+ id={id}
121
+ aria-hidden="true"
122
+ />
123
+ <span className="sd-switch__core" style={coreStyle}>
124
+ {loading && (
125
+ <span className="sd-switch__loading">
126
+ <svg viewBox="0 0 1024 1024" className="sd-switch__spinner">
127
+ <path
128
+ fill="currentColor"
129
+ d="M512 64a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V96a32 32 0 0 1 32-32zm0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32zm448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32zm-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32z"
130
+ />
131
+ </svg>
132
+ </span>
133
+ )}
134
+ <span className="sd-switch__action" />
135
+ {(currentChecked ? checkedNode : unCheckedNode) && (
136
+ <span className="sd-switch__content">
137
+ {currentChecked ? checkedNode : unCheckedNode}
138
+ </span>
139
+ )}
140
+ </span>
141
+ </div>
142
+ )
143
+ })
144
+
145
+ Switch.displayName = 'Switch'
@@ -0,0 +1,2 @@
1
+ export { Switch } from './Switch'
2
+ export type { SwitchProps } from './Switch'
@@ -0,0 +1,119 @@
1
+ /* Switch 组件样式 */
2
+ .sd-switch {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ position: relative;
6
+ cursor: pointer;
7
+ user-select: none;
8
+ outline: none;
9
+ }
10
+
11
+ .sd-switch:focus-visible .sd-switch__core {
12
+ outline: 2px solid var(--sd-color-primary);
13
+ outline-offset: 1px;
14
+ }
15
+
16
+ .sd-switch__input {
17
+ position: absolute;
18
+ width: 0;
19
+ height: 0;
20
+ opacity: 0;
21
+ margin: 0;
22
+ }
23
+
24
+ .sd-switch__core {
25
+ display: inline-flex;
26
+ position: relative;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: 50px;
30
+ height: 24px;
31
+ border: 1px solid var(--sd-border-color);
32
+ border-radius: 12px;
33
+ background-color: var(--sd-border-color-darker);
34
+ transition: all var(--sd-transition-duration-fast);
35
+ }
36
+
37
+ .sd-switch__action {
38
+ position: absolute;
39
+ left: 2px;
40
+ width: 20px;
41
+ height: 20px;
42
+ border-radius: 50%;
43
+ background-color: #ffffff;
44
+ transition: all var(--sd-transition-duration-fast);
45
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
46
+ z-index: 1;
47
+ }
48
+
49
+ .sd-switch__content {
50
+ position: absolute;
51
+ top: 50%;
52
+ transform: translateY(-50%);
53
+ font-size: 12px;
54
+ font-weight: 600;
55
+ color: #333333;
56
+ z-index: 3;
57
+ pointer-events: none;
58
+ transition: all var(--sd-transition-duration-fast);
59
+ line-height: 1;
60
+ text-align: center;
61
+ -webkit-font-smoothing: antialiased;
62
+ -moz-osx-font-smoothing: grayscale;
63
+ left: 6px;
64
+ }
65
+
66
+ /* 选中状态 */
67
+ .sd-switch.is-checked .sd-switch__core {
68
+ background-color: var(--sd-color-primary);
69
+ border-color: var(--sd-color-primary);
70
+ }
71
+
72
+ .sd-switch.is-checked .sd-switch__action {
73
+ left: calc(100% - 22px);
74
+ }
75
+
76
+ .sd-switch.is-checked .sd-switch__content {
77
+ left: calc(100% - 18px);
78
+ }
79
+
80
+ /* 加载状态 */
81
+ .sd-switch__loading {
82
+ position: absolute;
83
+ left: 50%;
84
+ top: 50%;
85
+ transform: translate(-50%, -50%);
86
+ display: inline-flex;
87
+ align-items: center;
88
+ color: var(--sd-color-white);
89
+ }
90
+
91
+ .sd-switch__spinner {
92
+ width: 12px;
93
+ height: 12px;
94
+ animation: sd-switch-spin 2s linear infinite;
95
+ }
96
+
97
+ @keyframes sd-switch-spin {
98
+ from {
99
+ transform: rotate(0deg);
100
+ }
101
+ to {
102
+ transform: rotate(360deg);
103
+ }
104
+ }
105
+
106
+ .sd-switch.is-loading .sd-switch__action {
107
+ opacity: 0;
108
+ }
109
+
110
+ /* 禁用状态 */
111
+ .sd-switch.is-disabled {
112
+ opacity: 0.6;
113
+ cursor: not-allowed;
114
+ }
115
+
116
+ .sd-switch.is-disabled .sd-switch__core {
117
+ background-color: var(--sd-disabled-bg-color);
118
+ border-color: var(--sd-disabled-border-color);
119
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "noEmit": true,
8
+ "skipLibCheck": true
9
+ },
10
+ "include": ["src/**/*", "../core/src/**/*"],
11
+ "exclude": ["node_modules", "dist"]
12
+ }
@@ -0,0 +1,31 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { resolve } from 'path'
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ build: {
8
+ lib: {
9
+ entry: resolve(__dirname, 'src/index.ts'),
10
+ name: 'SevenDesign',
11
+ formats: ['es', 'cjs'],
12
+ fileName: (format) => `index.${format === 'es' ? 'esm' : 'cjs'}.js`,
13
+ },
14
+ rollupOptions: {
15
+ external: ['react', 'react-dom', '@seven-design-ui/core'],
16
+ output: {
17
+ globals: {
18
+ react: 'React',
19
+ 'react-dom': 'ReactDOM',
20
+ '@seven-design-ui/core': 'SevenDesignCore',
21
+ },
22
+ assetFileNames: (assetInfo) => {
23
+ if (assetInfo.name === 'style.css') return 'style.css'
24
+ return assetInfo.name as string
25
+ },
26
+ },
27
+ },
28
+ outDir: 'dist',
29
+ emptyOutDir: true,
30
+ },
31
+ })
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@seven-design-ui/core",
3
+ "version": "0.1.0",
4
+ "description": "SevenDesign 核心工具和 Hooks",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "src"
11
+ ],
12
+ "scripts": {
13
+ "build": "vite build && tsc --emitDeclarationOnly",
14
+ "dev": "vite build --watch"
15
+ },
16
+ "peerDependencies": {
17
+ "react": ">=18.0.0",
18
+ "react-dom": ">=18.0.0"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ }
23
+ }
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useRef, useState } from 'react'
4
+
5
+ /**
6
+ * 受控/非受控状态 Hook
7
+ * 支持组件同时支持受控和非受控模式
8
+ */
9
+ export function useControllableState<T>(
10
+ value?: T,
11
+ defaultValue?: T,
12
+ onChange?: (value: T) => void
13
+ ): [T | undefined, (value: T) => void] {
14
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue)
15
+ const isControlled = value !== undefined
16
+ const stateValue = isControlled ? value : uncontrolledValue
17
+ const onChangeRef = useRef(onChange)
18
+ onChangeRef.current = onChange
19
+
20
+ const setState = useCallback(
21
+ (newValue: T) => {
22
+ if (!isControlled) {
23
+ setUncontrolledValue(newValue)
24
+ }
25
+ onChangeRef.current?.(newValue)
26
+ },
27
+ [isControlled]
28
+ )
29
+
30
+ return [stateValue, setState]
31
+ }
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react'
4
+
5
+ /**
6
+ * 事件监听 Hook
7
+ */
8
+ export function useEventListener<K extends keyof WindowEventMap>(
9
+ eventName: K,
10
+ handler: (event: WindowEventMap[K]) => void,
11
+ element?: HTMLElement | Window | null,
12
+ options?: boolean | AddEventListenerOptions
13
+ ) {
14
+ const savedHandler = useRef<(event: WindowEventMap[K]) => void>()
15
+
16
+ useEffect(() => {
17
+ savedHandler.current = handler
18
+ }, [handler])
19
+
20
+ useEffect(() => {
21
+ const targetElement = element ?? window
22
+
23
+ if (!targetElement || !targetElement.addEventListener) {
24
+ return
25
+ }
26
+
27
+ const eventListener = (event: Event) => {
28
+ savedHandler.current?.(event as WindowEventMap[K])
29
+ }
30
+
31
+ targetElement.addEventListener(eventName, eventListener, options)
32
+
33
+ return () => {
34
+ targetElement.removeEventListener(eventName, eventListener, options)
35
+ }
36
+ }, [eventName, element, options])
37
+ }
@@ -0,0 +1,7 @@
1
+ // Utils
2
+ export { classnames, createBEM } from './utils/classnames'
3
+ export type { ClassValue } from './utils/classnames'
4
+
5
+ // Hooks
6
+ export { useControllableState } from './hooks/useControllableState'
7
+ export { useEventListener } from './hooks/useEventListener'
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 类名合并工具
3
+ */
4
+ export type ClassValue =
5
+ | string
6
+ | number
7
+ | boolean
8
+ | undefined
9
+ | null
10
+ | ClassValue[]
11
+ | Record<string, boolean | undefined | null>
12
+
13
+ export function classnames(...classes: ClassValue[]): string {
14
+ const result: string[] = []
15
+
16
+ for (const cls of classes) {
17
+ if (!cls) continue
18
+
19
+ if (typeof cls === 'string' && cls.trim()) {
20
+ result.push(cls.trim())
21
+ } else if (typeof cls === 'number') {
22
+ result.push(String(cls))
23
+ } else if (Array.isArray(cls)) {
24
+ const inner = classnames(...cls)
25
+ if (inner) result.push(inner)
26
+ } else if (typeof cls === 'object') {
27
+ for (const key in cls) {
28
+ if (cls[key]) {
29
+ result.push(key)
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ return result.join(' ')
36
+ }
37
+
38
+ /**
39
+ * 创建 BEM 风格的类名生成器
40
+ */
41
+ export function createBEM(block: string) {
42
+ const b = () => block
43
+ const e = (element: string) => `${block}__${element}`
44
+ const m = (modifier: string) => `${block}--${modifier}`
45
+ const em = (element: string, modifier: string) => `${block}__${element}--${modifier}`
46
+
47
+ return { b, e, m, em }
48
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "noEmit": false
9
+ },
10
+ "include": ["src/**/*"],
11
+ "exclude": ["node_modules", "dist"]
12
+ }