Sfoglia il codice sorgente

[stdf]Public v2.0.0

dufu1991 7 mesi fa
parent
commit
818fab1ea4
100 ha cambiato i file con 7750 aggiunte e 1903 eliminazioni
  1. 130 91
      packages/stdf/package.json
  2. 79 51
      packages/stdf/src/app.css
  3. 1 1
      packages/stdf/src/app.html
  4. 1 0
      packages/stdf/src/assets/svgs/ri-apps-line.svg
  5. 1 0
      packages/stdf/src/assets/svgs/ri-arrow-left-line.svg
  6. 1 0
      packages/stdf/src/assets/svgs/ri-arrow-right-line.svg
  7. 1 0
      packages/stdf/src/assets/svgs/ri-clipboard-line.svg
  8. 1 0
      packages/stdf/src/assets/svgs/ri-delete-bin-line.svg
  9. 1 0
      packages/stdf/src/assets/svgs/ri-edit-line.svg
  10. 1 0
      packages/stdf/src/assets/svgs/ri-file-copy-line.svg
  11. 1 0
      packages/stdf/src/assets/svgs/ri-file-list-line.svg
  12. 1 0
      packages/stdf/src/assets/svgs/ri-filter-line.svg
  13. 1 0
      packages/stdf/src/assets/svgs/ri-gift-line.svg
  14. 1 0
      packages/stdf/src/assets/svgs/ri-heart-line.svg
  15. 1 0
      packages/stdf/src/assets/svgs/ri-more-2-fill.svg
  16. 1 0
      packages/stdf/src/assets/svgs/ri-notification-3-line.svg
  17. 1 0
      packages/stdf/src/assets/svgs/ri-play-line.svg
  18. 1 0
      packages/stdf/src/assets/svgs/ri-rocket-2-line.svg
  19. 1 0
      packages/stdf/src/assets/svgs/ri-scissors-line.svg
  20. 1 0
      packages/stdf/src/assets/svgs/ri-search-line.svg
  21. 1 0
      packages/stdf/src/assets/svgs/ri-skip-back-line.svg
  22. 1 0
      packages/stdf/src/assets/svgs/ri-skip-forward-line.svg
  23. 1 0
      packages/stdf/src/assets/svgs/ri-thumb-down-line.svg
  24. 1 0
      packages/stdf/src/assets/svgs/ri-thumb-up-line.svg
  25. 1 0
      packages/stdf/src/assets/svgs/ri-truck-line.svg
  26. 306 117
      packages/stdf/src/data/menuList.ts
  27. 0 59
      packages/stdf/src/data/themes/aqua.ts
  28. 0 59
      packages/stdf/src/data/themes/azuper.ts
  29. 0 59
      packages/stdf/src/data/themes/azurePink.ts
  30. 0 59
      packages/stdf/src/data/themes/camelliaRed.ts
  31. 0 59
      packages/stdf/src/data/themes/chameleon.ts
  32. 0 59
      packages/stdf/src/data/themes/citrus.ts
  33. 0 59
      packages/stdf/src/data/themes/greenJade.ts
  34. 0 31
      packages/stdf/src/data/themes/index.ts
  35. 0 59
      packages/stdf/src/data/themes/kleinH.ts
  36. 0 59
      packages/stdf/src/data/themes/lavender.ts
  37. 0 59
      packages/stdf/src/data/themes/nintendo.ts
  38. 0 59
      packages/stdf/src/data/themes/skybloom.ts
  39. 0 59
      packages/stdf/src/data/themes/smartLive.ts
  40. 0 59
      packages/stdf/src/data/themes/tealMedley.ts
  41. 150 0
      packages/stdf/src/lib/components/accordion/Accordion.svelte
  42. 484 0
      packages/stdf/src/lib/components/actionPopover/ActionPopover.svelte
  43. 33 10
      packages/stdf/src/lib/components/actionSheet/ActionSheet.svelte
  44. 259 0
      packages/stdf/src/lib/components/alert/Alert.svelte
  45. 29 13
      packages/stdf/src/lib/components/asyncPicker/AsyncPicker.svelte
  46. 3 13
      packages/stdf/src/lib/components/avatar/Avatar.svelte
  47. 32 48
      packages/stdf/src/lib/components/avatar/AvatarGroup.svelte
  48. 15 10
      packages/stdf/src/lib/components/badge/Badge.svelte
  49. 56 67
      packages/stdf/src/lib/components/bottomSheet/BottomSheet.svelte
  50. 101 37
      packages/stdf/src/lib/components/button/Button.svelte
  51. 183 0
      packages/stdf/src/lib/components/button/ButtonGroup.svelte
  52. 319 220
      packages/stdf/src/lib/components/calendar/Calendar.svelte
  53. 176 0
      packages/stdf/src/lib/components/card/Card.svelte
  54. 73 62
      packages/stdf/src/lib/components/cell/Cell.svelte
  55. 3 11
      packages/stdf/src/lib/components/cell/CellGroup.svelte
  56. 550 0
      packages/stdf/src/lib/components/charRoll/CharRoll.svelte
  57. 1 1
      packages/stdf/src/lib/components/checkbox/CheckboxItem.svelte
  58. 366 0
      packages/stdf/src/lib/components/codeInput/CodeInput.svelte
  59. 1042 0
      packages/stdf/src/lib/components/colorPicker/ColorPicker.svelte
  60. 200 0
      packages/stdf/src/lib/components/countDown/CountDown.svelte
  61. 1 1
      packages/stdf/src/lib/components/dialog/Dialog.svelte
  62. 218 0
      packages/stdf/src/lib/components/feedback/FeedbackContainer.svelte
  63. 291 0
      packages/stdf/src/lib/components/feedback/index.ts
  64. 78 0
      packages/stdf/src/lib/components/feedback/state.svelte.ts
  65. 39 0
      packages/stdf/src/lib/components/feedback/state.types.ts
  66. 516 276
      packages/stdf/src/lib/components/form/Form.svelte
  67. 330 0
      packages/stdf/src/lib/components/fullKeyboard/FullKeyboard.svelte
  68. 15 2
      packages/stdf/src/lib/components/icon/Icon.svelte
  69. 260 0
      packages/stdf/src/lib/components/imageList/ImageList.svelte
  70. 565 0
      packages/stdf/src/lib/components/imagePreview/ImagePreview.svelte
  71. 15 18
      packages/stdf/src/lib/components/indexBar/IndexBar.svelte
  72. 95 25
      packages/stdf/src/lib/components/input/Input.svelte
  73. 683 0
      packages/stdf/src/lib/components/list/List.svelte
  74. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_10.svelte
  75. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_11.svelte
  76. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_16.svelte
  77. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_17.svelte
  78. 2 2
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_22.svelte
  79. 2 2
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_23.svelte
  80. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_27.svelte
  81. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_28.svelte
  82. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_29.svelte
  83. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_30.svelte
  84. 2 2
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_31.svelte
  85. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_39.svelte
  86. 2 2
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_4.svelte
  87. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_40.svelte
  88. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_42.svelte
  89. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_43.svelte
  90. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_44.svelte
  91. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_45.svelte
  92. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_46.svelte
  93. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_47.svelte
  94. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_48.svelte
  95. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_49.svelte
  96. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_50.svelte
  97. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_51.svelte
  98. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_52.svelte
  99. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_53.svelte
  100. 1 1
      packages/stdf/src/lib/components/loading/loadings/oneColor/1_9.svelte

+ 130 - 91
packages/stdf/package.json

@@ -1,6 +1,6 @@
 {
 	"name": "stdf",
-	"version": "1.2.0",
+	"version": "2.0.0",
 	"description": "Mobile web component library based on Svelte and Tailwind",
 	"author": "any-tdf",
 	"funding": "https://stdf.design?fund",
@@ -18,8 +18,8 @@
 		"移动组件库",
 		"moblie ui",
 		"web component",
-		"svelte v5",
-		"tailwind v4"
+		"svelte",
+		"tailwind"
 	],
 	"scripts": {
 		"dev": "vite dev",
@@ -32,84 +32,122 @@
 		"format": "prettier --write .",
 		"lint": "prettier --check . && eslint .",
 		"dev_en": "vite --mode english",
+		"accordion": "vite --mode accordion",
+		"actionPopover": "vite --mode actionPopover",
+		"actionSheet": "vite --mode actionSheet",
+		"alert": "vite --mode alert",
+		"asyncPicker": "vite --mode asyncPicker",
+		"avatar": "vite --mode avatar",
+		"avatarGroup": "vite --mode avatarGroup",
+		"badge": "vite --mode badge",
+		"bottomSheet": "vite --mode bottomSheet",
 		"button": "vite --mode button",
-		"icon": "vite --mode icon",
-		"navBar": "vite --mode navBar",
-		"divider": "vite --mode divider",
+		"buttonGroup": "vite --mode buttonGroup",
+		"calendar": "vite --mode calendar",
+		"card": "vite --mode card",
 		"cell": "vite --mode cell",
-		"switch": "vite --mode switch",
-		"avatar": "vite --mode avatar",
-		"tabs": "vite --mode tabs",
-		"tabBar": "vite --mode tabBar",
-		"steps": "vite --mode steps",
-		"indexBar": "vite --mode indexBar",
-		"radio": "vite --mode radio",
+		"charRoll": "vite --mode charRoll",
 		"checkbox": "vite --mode checkbox",
-		"input": "vite --mode input",
+		"codeInput": "vite --mode codeInput",
+		"colorPicker": "vite --mode colorPicker",
+		"countDown": "vite --mode countDown",
+		"dialog": "vite --mode dialog",
+		"divider": "vite --mode divider",
+		"feedback": "vite --mode feedback",
+		"form": "vite --mode form",
+		"fullKeyboard": "vite --mode fullKeyboard",
 		"grids": "vite --mode grids",
-		"swiper": "vite --mode swiper",
-		"placeholder": "vite --mode placeholder",
-		"skeleton": "vite --mode skeleton",
-		"rate": "vite --mode rate",
-		"slider": "vite --mode slider",
-		"badge": "vite --mode badge",
-		"noticeBar": "vite --mode noticeBar",
-		"progress": "vite --mode progress",
-		"progressLoop": "vite --mode progressLoop",
-		"mask": "vite --mode mask",
-		"toast": "vite --mode toast",
+		"icon": "vite --mode icon",
+		"imageList": "vite --mode imageList",
+		"imagePreview": "vite --mode imagePreview",
+		"indexBar": "vite --mode indexBar",
+		"input": "vite --mode input",
+		"list": "vite --mode list",
 		"loading": "vite --mode loading",
-		"popup": "vite --mode popup",
-		"bottomSheet": "vite --mode bottomSheet",
+		"mask": "vite --mode mask",
 		"modal": "vite --mode modal",
-		"dialog": "vite --mode dialog",
-		"actionSheet": "vite --mode actionSheet",
+		"navBar": "vite --mode navBar",
+		"noticeBar": "vite --mode noticeBar",
+		"numKeyboard": "vite --mode numKeyboard",
+		"pagination": "vite --mode pagination",
 		"picker": "vite --mode picker",
-		"asyncPicker": "vite --mode asyncPicker",
+		"placeholder": "vite --mode placeholder",
+		"popup": "vite --mode popup",
+		"progress": "vite --mode progress",
+		"progressLoop": "vite --mode progressLoop",
+		"radio": "vite --mode radio",
+		"rate": "vite --mode rate",
+		"signature": "vite --mode signature",
+		"skeleton": "vite --mode skeleton",
+		"slider": "vite --mode slider",
+		"stepper": "vite --mode stepper",
+		"steps": "vite --mode steps",
+		"swiper": "vite --mode swiper",
+		"switch": "vite --mode switch",
+		"tabBar": "vite --mode tabBar",
+		"tabs": "vite --mode tabs",
+		"tag": "vite --mode tag",
 		"timePicker": "vite --mode timePicker",
-		"calendar": "vite --mode calendar",
-		"pagination": "vite --mode pagination",
-		"numKeyboard": "vite --mode numKeyboard",
-		"form": "vite --mode form",
+		"toast": "vite --mode toast",
+		"tooltip": "vite --mode tooltip",
+		"accordion_en": "vite --mode accordion_en",
+		"actionPopover_en": "vite --mode actionPopover_en",
+		"actionSheet_en": "vite --mode actionSheet_en",
+		"alert_en": "vite --mode alert_en",
+		"asyncPicker_en": "vite --mode asyncPicker_en",
+		"avatar_en": "vite --mode avatar_en",
+		"avatarGroup_en": "vite --mode avatarGroup_en",
+		"badge_en": "vite --mode badge_en",
+		"bottomSheet_en": "vite --mode bottomSheet_en",
 		"button_en": "vite --mode button_en",
-		"icon_en": "vite --mode icon_en",
-		"navBar_en": "vite --mode navBar_en",
-		"divider_en": "vite --mode divider_en",
+		"buttonGroup_en": "vite --mode buttonGroup_en",
+		"calendar_en": "vite --mode calendar_en",
+		"card_en": "vite --mode card_en",
 		"cell_en": "vite --mode cell_en",
-		"switch_en": "vite --mode switch_en",
-		"avatar_en": "vite --mode avatar_en",
-		"tabs_en": "vite --mode tabs_en",
-		"tabBar_en": "vite --mode tabBar_en",
-		"steps_en": "vite --mode steps_en",
-		"indexBar_en": "vite --mode indexBar_en",
-		"radio_en": "vite --mode radio_en",
+		"charRoll_en": "vite --mode charRoll_en",
 		"checkbox_en": "vite --mode checkbox_en",
-		"input_en": "vite --mode input_en",
+		"codeInput_en": "vite --mode codeInput_en",
+		"colorPicker_en": "vite --mode colorPicker_en",
+		"countDown_en": "vite --mode countDown_en",
+		"dialog_en": "vite --mode dialog_en",
+		"divider_en": "vite --mode divider_en",
+		"feedback_en": "vite --mode feedback_en",
+		"form_en": "vite --mode form_en",
+		"fullKeyboard_en": "vite --mode fullKeyboard_en",
 		"grids_en": "vite --mode grids_en",
-		"swiper_en": "vite --mode swiper_en",
-		"placeholder_en": "vite --mode placeholder_en",
-		"skeleton_en": "vite --mode skeleton_en",
-		"rate_en": "vite --mode rate_en",
-		"slider_en": "vite --mode slider_en",
-		"badge_en": "vite --mode badge_en",
-		"noticeBar_en": "vite --mode noticeBar_en",
-		"progress_en": "vite --mode progress_en",
-		"progressLoop_en": "vite --mode progressLoop_en",
-		"mask_en": "vite --mode mask_en",
-		"toast_en": "vite --mode toast_en",
+		"icon_en": "vite --mode icon_en",
+		"imageList_en": "vite --mode imageList_en",
+		"imagePreview_en": "vite --mode imagePreview_en",
+		"indexBar_en": "vite --mode indexBar_en",
+		"input_en": "vite --mode input_en",
+		"list_en": "vite --mode list_en",
 		"loading_en": "vite --mode loading_en",
-		"popup_en": "vite --mode popup_en",
-		"bottomSheet_en": "vite --mode bottomSheet_en",
+		"mask_en": "vite --mode mask_en",
 		"modal_en": "vite --mode modal_en",
-		"dialog_en": "vite --mode dialog_en",
-		"actionSheet_en": "vite --mode actionSheet_en",
+		"navBar_en": "vite --mode navBar_en",
+		"noticeBar_en": "vite --mode noticeBar_en",
+		"numKeyboard_en": "vite --mode numKeyboard_en",
+		"pagination_en": "vite --mode pagination_en",
 		"picker_en": "vite --mode picker_en",
-		"asyncPicker_en": "vite --mode asyncPicker_en",
+		"placeholder_en": "vite --mode placeholder_en",
+		"popup_en": "vite --mode popup_en",
+		"progress_en": "vite --mode progress_en",
+		"progressLoop_en": "vite --mode progressLoop_en",
+		"radio_en": "vite --mode radio_en",
+		"rate_en": "vite --mode rate_en",
+		"signature_en": "vite --mode signature_en",
+		"skeleton_en": "vite --mode skeleton_en",
+		"slider_en": "vite --mode slider_en",
+		"stepper_en": "vite --mode stepper_en",
+		"steps_en": "vite --mode steps_en",
+		"swiper_en": "vite --mode swiper_en",
+		"switch_en": "vite --mode switch_en",
+		"tabBar_en": "vite --mode tabBar_en",
+		"tabs_en": "vite --mode tabs_en",
+		"tag_en": "vite --mode tag_en",
 		"timePicker_en": "vite --mode timePicker_en",
-		"calendar_en": "vite --mode calendar_en",
-		"pagination_en": "vite --mode pagination_en",
-		"numKeyboard_en": "vite --mode numKeyboard_en",
-		"form_en": "vite --mode form_en"
+		"toast_en": "vite --mode toast_en",
+		"tooltip_en": "vite --mode tooltip_en"
 	},
 	"files": [
 		"dist",
@@ -133,7 +171,8 @@
 		},
 		"./theme": {
 			"types": "./dist/theme/index.d.ts",
-			"svelte": "./dist/theme/index.js"
+			"svelte": "./dist/theme/index.js",
+			"default": "./dist/theme/plugin.js"
 		},
 		"./lang": {
 			"types": "./dist/lang/index.d.ts",
@@ -149,31 +188,31 @@
 		"tailwindcss": "^4.0.0"
 	},
 	"devDependencies": {
-		"@iconify-json/carbon": "^1.2.8",
-		"@iconify-json/fluent-color": "^1.2.12",
-		"@iconify-json/material-symbols": "^1.2.21",
-		"@iconify-json/solar": "^1.2.2",
-		"@iconify/tailwind4": "^1.0.6",
-		"@sveltejs/adapter-static": "^3.0.8",
-		"@sveltejs/kit": "^2.21.2",
-		"@sveltejs/package": "^2.3.10",
-		"@sveltejs/vite-plugin-svelte": "^5.0.3",
-		"@tailwindcss/vite": "^4.0.12",
-		"eslint": "^9.22.0",
-		"eslint-config-prettier": "^10.1.1",
-		"eslint-plugin-svelte": "^3.1.0",
-		"globals": "^16.0.0",
-		"prettier": "^3.5.3",
-		"prettier-plugin-svelte": "^3.3.3",
-		"prettier-plugin-tailwindcss": "^0.6.11",
-		"publint": "^0.3.9",
+		"@iconify-json/carbon": "^1.2.18",
+		"@iconify-json/fluent-color": "^1.2.21",
+		"@iconify-json/material-symbols": "^1.2.52",
+		"@iconify-json/solar": "^1.2.5",
+		"@iconify/tailwind4": "^1.2.1",
+		"@sveltejs/adapter-static": "^3.0.10",
+		"@sveltejs/kit": "^2.50.0",
+		"@sveltejs/package": "^2.5.7",
+		"@sveltejs/vite-plugin-svelte": "^5.1.1",
+		"@tailwindcss/vite": "^4.1.18",
+		"eslint": "^9.39.2",
+		"eslint-config-prettier": "^10.1.8",
+		"eslint-plugin-svelte": "^3.14.0",
+		"globals": "^16.5.0",
+		"prettier": "^3.8.1",
+		"prettier-plugin-svelte": "^3.4.1",
+		"prettier-plugin-tailwindcss": "^0.6.14",
+		"publint": "^0.3.17",
 		"rollup-plugin-stdf-icon": "file:../rollup-plugin-stdf-icon",
-		"svelte": "^5.22.6",
-		"svelte-check": "^4.1.5",
-		"svelte-confetti": "^2.3.1",
-		"tailwindcss": "^4.0.12",
-		"typescript": "^5.8.2",
-		"typescript-eslint": "^8.26.1",
-		"vite": "^6.2.1"
+		"svelte": "^5.48.0",
+		"svelte-check": "^4.3.5",
+		"svelte-confetti": "^2.3.2",
+		"tailwindcss": "^4.1.18",
+		"typescript": "^5.9.3",
+		"typescript-eslint": "^8.53.1",
+		"vite": "^6.4.1"
 	}
 }

+ 79 - 51
packages/stdf/src/app.css

@@ -1,74 +1,97 @@
 @import 'tailwindcss';
 
-@custom-variant dark (&:where(.dark, .dark *));
+@custom-variant dark (&:where([data-mode=dark], [data-mode=dark] *):not(:where([data-mode=light], [data-mode=light] *):not([data-mode=dark], [data-mode=dark] *)));
 
 @plugin "@iconify/tailwind4" {
 	prefixes: material-symbols, solar, carbon, fluent-color;
 }
 
 @theme {
-	--color-primary-50: oklch(0.979 0.01 267.36);
-	--color-primary-100: oklch(0.95 0.024 270.343);
-	--color-primary-200: oklch(0.847 0.074 271.188);
-	--color-primary-300: oklch(0.741 0.13 272.232);
-	--color-primary-400: oklch(0.634 0.193 271.595);
-	--color-primary-500: oklch(0.536 0.252 268.66);
-	--color-primary: oklch(0.467 0.296 264.886);
-	--color-primary-700: oklch(0.397 0.26 264.877);
-	--color-primary-800: oklch(0.331 0.221 264.833);
-	--color-primary-900: oklch(0.26 0.178 264.428);
-	--color-primary-950: oklch(0.192 0.13 266.64);
+	--color-primary-50: oklch(0.97 0.036 276.886); /* #eef4ff rgb(238, 244, 255) */
+	--color-primary-100: oklch(0.882 0.071 274.886); /* #cad5ff rgb(202, 213, 255) */
+	--color-primary-200: oklch(0.795 0.105 272.886); /* #a5b8ff rgb(165, 184, 255) */
+	--color-primary-300: oklch(0.707 0.14 270.886); /* #809af8 rgb(128, 154, 248) */
+	--color-primary-400: oklch(0.62 0.174 268.886); /* #5c7cee rgb(92, 124, 238) */
+	--color-primary-500: oklch(0.532 0.209 266.886); /* #365ce3 rgb(54, 92, 227) */
+	--color-primary: oklch(0.444 0.243 264.886); /* #0e36d6 rgb(14, 54, 214) */
+	--color-primary-700: oklch(0.396 0.263 262.886); /* #0015d0 rgb(0, 21, 208) */
+	--color-primary-800: oklch(0.347 0.267 260.886); /* #0000c0 rgb(0, 0, 192) */
+	--color-primary-900: oklch(0.299 0.267 258.886); /* #0000af rgb(0, 0, 175) */
+	--color-primary-950: oklch(0.25 0.267 256.886); /* #01009d rgb(1, 0, 157) */
 
-	--color-dark-50: oklch(0.995 0.012 101.474);
-	--color-dark-100: oklch(0.986 0.025 97.107);
-	--color-dark-200: oklch(0.965 0.059 95.884);
-	--color-dark-300: oklch(0.938 0.089 92.952);
-	--color-dark-400: oklch(0.91 0.114 89.711);
-	--color-dark-500: oklch(0.88 0.136 86.375);
-	--color-dark: oklch(0.845 0.153 80.597);
-	--color-dark-700: oklch(0.71 0.137 75.734);
-	--color-dark-800: oklch(0.58 0.118 70.166);
-	--color-dark-900: oklch(0.449 0.097 65.209);
-	--color-dark-950: oklch(0.321 0.074 62.379);
+	--color-dark-50: oklch(0.97 0.023 68.597); /* #fff3e5 rgb(255, 243, 229) */
+	--color-dark-100: oklch(0.949 0.045 70.597); /* #ffeace rgb(255, 234, 206) */
+	--color-dark-200: oklch(0.928 0.066 72.597); /* #ffe1b7 rgb(255, 225, 183) */
+	--color-dark-300: oklch(0.907 0.088 74.597); /* #ffd89f rgb(255, 216, 159) */
+	--color-dark-400: oklch(0.887 0.11 76.597); /* #ffd084 rgb(255, 208, 132) */
+	--color-dark-500: oklch(0.866 0.131 78.597); /* #ffc868 rgb(255, 200, 104) */
+	--color-dark: oklch(0.845 0.153 80.597); /* #ffc042 rgb(255, 192, 66) */
+	--color-dark-700: oklch(0.696 0.168 82.597); /* #cf9000 rgb(207, 144, 0) */
+	--color-dark-800: oklch(0.547 0.168 84.597); /* #9d6400 rgb(157, 100, 0) */
+	--color-dark-900: oklch(0.399 0.168 86.597); /* #6e3a00 rgb(110, 58, 0) */
+	--color-dark-950: oklch(0.25 0.168 88.597); /* #411000 rgb(65, 16, 0) */
 
-	--color-primaryBlack: oklch(0.116 0.054 267.087);
-	--color-primaryWhite: oklch(0.961 0.001 286.375);
-	--color-darkBlack: oklch(0.183 0.035 86.634);
-	--color-darkWhite: oklch(0.962 0.001 17.178);
+	--color-success: oklch(0.704 0.142 167.084); /* #0fbb8d rgb(15, 187, 141) */
+	--color-warning: oklch(0.558 0.153 47.186); /* #b95002 rgb(185, 80, 2) */
+	--color-error: oklch(0.564 0.223 28.46); /* #da1514 rgb(218, 21, 20) */
+	--color-info: oklch(0.482 0.14 261.518); /* #2e5aac rgb(46, 90, 172) */
 
-	--color-success: oklch(0.704 0.142 167.084);
-	--color-warning: oklch(0.558 0.154 47.186);
-	--color-error: oklch(0.564 0.223 28.46);
-	--color-info: oklch(0.482 0.14 261.518);
+	--color-blue: oklch(0.6 0.2 250); /* #0081f1 rgb(0, 129, 241) */
+	--color-purple: oklch(0.6 0.2 300); /* #955be3 rgb(149, 91, 227) */
+	--color-orange: oklch(0.7 0.18 50); /* #f37513 rgb(243, 117, 19) */
+	--color-cyan: oklch(0.7 0.15 190); /* #00bbb3 rgb(0, 187, 179) */
 
-	--color-extend0: oklch(0.703 0.149 235.059);
-	--color-Twitter: oklch(0.703 0.149 235.059);
-	--color-extend1: oklch(0.702 0.194 38.137);
-	--color-Svelte: oklch(0.702 0.194 38.137);
-	--color-extend2: oklch(0.482 0.107 161.212);
-	--color-Starbucks: oklch(0.482 0.107 161.212);
-
-	--color-black: oklch(0 0 0);
-	--color-white: oklch(1 0 0);
-	--color-gray-50: oklch(0.961 0 0);
-	--color-gray-100: oklch(0.925 0 0);
-	--color-gray-200: oklch(0.845 0 0);
-	--color-gray-300: oklch(0.767 0 0);
-	--color-gray-400: oklch(0.683 0 0);
-	--color-gray-500: oklch(0.6 0 0);
-	--color-gray-600: oklch(0.51 0 0);
-	--color-gray-700: oklch(0.42 0 0);
-	--color-gray-800: oklch(0.321 0 0);
-	--color-gray-900: oklch(0.218 0 0);
-	--color-gray-950: oklch(0.159 0 0);
+	--color-black: oklch(0 0 0); /* #000000 rgb(0, 0, 0) */
+	--color-white: oklch(1 0 0); /* #FFFFFF rgb(255, 255, 255) */
+	--color-gray-50: oklch(0.961 0 0); /* #F5F5F5 rgb(245, 245, 245) */
+	--color-gray-100: oklch(0.925 0 0); /* #E8E8E8 rgb(232, 232, 232) */
+	--color-gray-200: oklch(0.845 0 0); /* #D1D1D1 rgb(209, 209, 209) */
+	--color-gray-300: oklch(0.767 0 0); /* #B8B8B8 rgb(184, 184, 184) */
+	--color-gray-400: oklch(0.683 0 0); /* #9E9E9E rgb(158, 158, 158) */
+	--color-gray-500: oklch(0.6 0 0); /* #858585 rgb(133, 133, 133) */
+	--color-gray-600: oklch(0.51 0 0); /* #6B6B6B rgb(107, 107, 107) */
+	--color-gray-700: oklch(0.42 0 0); /* #525252 rgb(82, 82, 82) */
+	--color-gray-800: oklch(0.321 0 0); /* #383838 rgb(56, 56, 56) */
+	--color-gray-900: oklch(0.218 0 0); /* #1F1F1F rgb(31, 31, 31) */
+	--color-gray-950: oklch(0.159 0 0); /* #121212 rgb(18, 18, 18) */
 	--color-transparent: transparent;
 
+	--color-bg-base: oklch(0.967 0.016 264.84); /* #eff4ff rgb(239, 244, 255) */
+	--color-bg-surface: oklch(0.978 0 80.342); /* #f8f8f8 rgb(248, 248, 248) */
+	--color-bg-overlay: oklch(0.961 0 93.125); /* #f2f2f2 rgb(242, 242, 242) */
+	--color-bg-highlight: oklch(0.961 0.017 279.375); /* #eff1fe rgb(239, 241, 254) */
+	--color-bg-base-dark: oklch(0.167 0.015 80.342); /* #120e07 rgb(18, 14, 7) */
+	--color-bg-surface-dark: oklch(0.178 0.017 264.84); /* #0d1119 rgb(13, 17, 25) */
+	--color-bg-overlay-dark: oklch(0.233 0.001 264.84); /* #1d1e1e rgb(29, 30, 30) */
+	--color-bg-highlight-dark: oklch(0.178 0.019 80.342); /* #161008 rgb(22, 16, 8) */
+
+	--color-text-primary: oklch(0.144 0.016 245.938); /* #050b10 rgb(5, 11, 16) */
+	--color-text-dark: oklch(0.917 0.039 89.688); /* #eee3c7 rgb(238, 227, 199) */
+	--color-text-on-primary: oklch(0.883 0.043 79.063); /* #e8d6b9 rgb(232, 214, 185) */
+	--color-text-on-dark: oklch(0.189 0.05 262.813); /* #07122a rgb(7, 18, 42) */
+
+	--radius-box: 0.5rem;
+	--radius-form: 0.25rem;
+	--radius-small: calc(infinity * 1px);
+
 	--font-Trueno: 'Trueno', Trueno;
 }
+
 @font-face {
 	font-family: Trueno;
 	src: url('/fonts/Trueno.otf') format('opentype');
 }
+
+/* CodeInput cursor blink animation */
+@keyframes blink {
+	0%,
+	100% {
+		opacity: 1;
+	}
+	50% {
+		opacity: 0;
+	}
+}
 *::-webkit-scrollbar {
 	width: 6px;
 }
@@ -96,3 +119,8 @@ body {
 button:focus {
 	outline: none;
 }
+
+/* 使用插件生成所有内置主题 Use plugin to generate all built-in themes */
+@plugin "stdf/theme" {
+	all: true;
+}

+ 1 - 1
packages/stdf/src/app.html

@@ -11,7 +11,7 @@
 
 	<body
 		data-sveltekit-preload-data="hover"
-		class="bg-primaryWhite dark:bg-darkBlack relative min-h-screen w-screen text-justify text-black antialiased dark:text-white"
+		class="bg-bg-base dark:bg-bg-base-dark text-text-primary dark:text-text-dark antialiased relative min-h-screen w-screen text-justify "
 	>
 		<main style="display: contents">%sveltekit.body%</main>
 	</body>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-apps-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6.75 2.5C9.09721 2.5 11 4.40279 11 6.75V11H6.75C4.40279 11 2.5 9.09721 2.5 6.75C2.5 4.40279 4.40279 2.5 6.75 2.5ZM9 9V6.75C9 5.50736 7.99264 4.5 6.75 4.5C5.50736 4.5 4.5 5.50736 4.5 6.75C4.5 7.99264 5.50736 9 6.75 9H9ZM6.75 13H11V17.25C11 19.5972 9.09721 21.5 6.75 21.5C4.40279 21.5 2.5 19.5972 2.5 17.25C2.5 14.9028 4.40279 13 6.75 13ZM6.75 15C5.50736 15 4.5 16.0074 4.5 17.25C4.5 18.4926 5.50736 19.5 6.75 19.5C7.99264 19.5 9 18.4926 9 17.25V15H6.75ZM17.25 2.5C19.5972 2.5 21.5 4.40279 21.5 6.75C21.5 9.09721 19.5972 11 17.25 11H13V6.75C13 4.40279 14.9028 2.5 17.25 2.5ZM17.25 9C18.4926 9 19.5 7.99264 19.5 6.75C19.5 5.50736 18.4926 4.5 17.25 4.5C16.0074 4.5 15 5.50736 15 6.75V9H17.25ZM13 13H17.25C19.5972 13 21.5 14.9028 21.5 17.25C21.5 19.5972 19.5972 21.5 17.25 21.5C14.9028 21.5 13 19.5972 13 17.25V13ZM15 15V17.25C15 18.4926 16.0074 19.5 17.25 19.5C18.4926 19.5 19.5 18.4926 19.5 17.25C19.5 16.0074 18.4926 15 17.25 15H15Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-arrow-left-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M7.82843 10.9999H20V12.9999H7.82843L13.1924 18.3638L11.7782 19.778L4 11.9999L11.7782 4.22168L13.1924 5.63589L7.82843 10.9999Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-arrow-right-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M16.1716 10.9999L10.8076 5.63589L12.2218 4.22168L20 11.9999L12.2218 19.778L10.8076 18.3638L16.1716 12.9999H4V10.9999H16.1716Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-clipboard-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4V2H17V4H20.0066C20.5552 4 21 4.44495 21 4.9934V21.0066C21 21.5552 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5551 3 21.0066V4.9934C3 4.44476 3.44495 4 3.9934 4H7ZM7 6H5V20H19V6H17V8H7V6ZM9 4V6H15V4H9Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-delete-bin-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 6H22V8H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V8H2V6H7V3C7 2.44772 7.44772 2 8 2H16C16.5523 2 17 2.44772 17 3V6ZM18 8H6V20H18V8ZM9 11H11V17H9V11ZM13 11H15V17H13V11ZM9 4V6H15V4H9Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-edit-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6.41421 15.89L16.5563 5.74785L15.1421 4.33363L5 14.4758V15.89H6.41421ZM7.24264 17.89H3V13.6473L14.435 2.21231C14.8256 1.82179 15.4587 1.82179 15.8492 2.21231L18.6777 5.04074C19.0682 5.43126 19.0682 6.06443 18.6777 6.45495L7.24264 17.89ZM3 19.89H21V21.89H3V19.89Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-file-copy-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6.9998 6V3C6.9998 2.44772 7.44752 2 7.9998 2H19.9998C20.5521 2 20.9998 2.44772 20.9998 3V17C20.9998 17.5523 20.5521 18 19.9998 18H16.9998V20.9991C16.9998 21.5519 16.5499 22 15.993 22H4.00666C3.45059 22 3 21.5554 3 20.9991L3.0026 7.00087C3.0027 6.44811 3.45264 6 4.00942 6H6.9998ZM5.00242 8L5.00019 20H14.9998V8H5.00242ZM8.9998 6H16.9998V16H18.9998V4H8.9998V6Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-file-list-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20 22H4C3.44772 22 3 21.5523 3 21V3C3 2.44772 3.44772 2 4 2H20C20.5523 2 21 2.44772 21 3V21C21 21.5523 20.5523 22 20 22ZM19 20V4H5V20H19ZM8 7H16V9H8V7ZM8 11H16V13H8V11ZM8 15H16V17H8V15Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-filter-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M21 4V6H20L15 13.5V22H9V13.5L4 6H3V4H21ZM6.4037 6L11 12.8944V20H13V12.8944L17.5963 6H6.4037Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-gift-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M15.0049 2.00281C17.214 2.00281 19.0049 3.79367 19.0049 6.00281C19.0049 6.73184 18.8098 7.41532 18.4691 8.00392L23.0049 8.00281V10.0028H21.0049V20.0028C21.0049 20.5551 20.5572 21.0028 20.0049 21.0028H4.00488C3.4526 21.0028 3.00488 20.5551 3.00488 20.0028V10.0028H1.00488V8.00281L5.54065 8.00392C5.19992 7.41532 5.00488 6.73184 5.00488 6.00281C5.00488 3.79367 6.79574 2.00281 9.00488 2.00281C10.2001 2.00281 11.2729 2.52702 12.0058 3.35807C12.7369 2.52702 13.8097 2.00281 15.0049 2.00281ZM11.0049 10.0028H5.00488V19.0028H11.0049V10.0028ZM19.0049 10.0028H13.0049V19.0028H19.0049V10.0028ZM9.00488 4.00281C7.90031 4.00281 7.00488 4.89824 7.00488 6.00281C7.00488 7.05717 7.82076 7.92097 8.85562 7.99732L9.00488 8.00281H11.0049V6.00281C11.0049 5.00116 10.2686 4.1715 9.30766 4.02558L9.15415 4.00829L9.00488 4.00281ZM15.0049 4.00281C13.9505 4.00281 13.0867 4.81869 13.0104 5.85355L13.0049 6.00281V8.00281H15.0049C16.0592 8.00281 16.923 7.18693 16.9994 6.15207L17.0049 6.00281C17.0049 4.89824 16.1095 4.00281 15.0049 4.00281Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-heart-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12.001 4.52853C14.35 2.42 17.98 2.49 20.2426 4.75736C22.5053 7.02472 22.583 10.637 20.4786 12.993L11.9999 21.485L3.52138 12.993C1.41705 10.637 1.49571 7.01901 3.75736 4.75736C6.02157 2.49315 9.64519 2.41687 12.001 4.52853ZM18.827 6.1701C17.3279 4.66794 14.9076 4.60701 13.337 6.01687L12.0019 7.21524L10.6661 6.01781C9.09098 4.60597 6.67506 4.66808 5.17157 6.17157C3.68183 7.66131 3.60704 10.0473 4.97993 11.6232L11.9999 18.6543L19.0201 11.6232C20.3935 10.0467 20.319 7.66525 18.827 6.1701Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-more-2-fill.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 3C10.9 3 10 3.9 10 5C10 6.1 10.9 7 12 7C13.1 7 14 6.1 14 5C14 3.9 13.1 3 12 3ZM12 17C10.9 17 10 17.9 10 19C10 20.1 10.9 21 12 21C13.1 21 14 20.1 14 19C14 17.9 13.1 17 12 17ZM12 10C10.9 10 10 10.9 10 12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12C14 10.9 13.1 10 12 10Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-notification-3-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20 17H22V19H2V17H4V10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10V17ZM18 17V10C18 6.68629 15.3137 4 12 4C8.68629 4 6 6.68629 6 10V17H18ZM9 21H15V23H9V21Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-play-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M16.3944 12.0001L10 7.7371V16.263L16.3944 12.0001ZM19.376 12.4161L8.77735 19.4818C8.54759 19.635 8.23715 19.5729 8.08397 19.3432C8.02922 19.261 8 19.1645 8 19.0658V4.93433C8 4.65818 8.22386 4.43433 8.5 4.43433C8.59871 4.43433 8.69522 4.46355 8.77735 4.5183L19.376 11.584C19.6057 11.7372 19.6678 12.0477 19.5146 12.2774C19.478 12.3323 19.4309 12.3795 19.376 12.4161Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-rocket-2-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M15.5018 20.0048C14.8432 21.5841 13.5794 22.848 12 23.5066C10.4206 22.848 9.15679 21.5841 8.49816 20.0048H10.7575C11.0838 20.4934 11.5051 20.9171 12 21.248C12.4949 20.9171 12.9162 20.4934 13.2425 20.0048H15.5018ZM18 14.8095L20 17.0777V19.0048H4V17.0777L6 14.8095V9.00478C6 5.52153 8.50442 2.55822 12 1.45999C15.4956 2.55822 18 5.52153 18 9.00478V14.8095ZM17.2692 17.0048L16 15.5653V9.00478C16 6.68694 14.4292 4.57431 12 3.58384C9.57078 4.57431 8 6.68694 8 9.00478V15.5653L6.73076 17.0048H17.2692ZM12 11.0048C10.8954 11.0048 10 10.1093 10 9.00478C10 7.90021 10.8954 7.00478 12 7.00478C13.1046 7.00478 14 7.90021 14 9.00478C14 10.1093 13.1046 11.0048 12 11.0048Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-scissors-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M9.44618 8.02867L12 10.5825L18.7279 3.85457C19.509 3.07352 20.7753 3.07352 21.5563 3.85457L9.44618 15.9647C9.79807 16.5603 10 17.2549 10 17.9967C10 20.2058 8.20914 21.9967 6 21.9967C3.79086 21.9967 2 20.2058 2 17.9967C2 15.7876 3.79086 13.9967 6 13.9967C6.74181 13.9967 7.43645 14.1986 8.03197 14.5505L10.5858 11.9967L8.03197 9.44289C7.43645 9.79478 6.74181 9.9967 6 9.9967C3.79086 9.9967 2 8.20584 2 5.9967C2 3.78756 3.79086 1.9967 6 1.9967C8.20914 1.9967 10 3.78756 10 5.9967C10 6.73851 9.79807 7.43316 9.44618 8.02867ZM14.8255 13.408L21.5563 20.1388C20.7753 20.9199 19.509 20.9199 18.7279 20.1388L13.4113 14.8222L14.8255 13.408ZM7.41421 16.5825C7.05228 16.2206 6.55228 15.9967 6 15.9967C4.89543 15.9967 4 16.8921 4 17.9967C4 19.1013 4.89543 19.9967 6 19.9967C7.10457 19.9967 8 19.1013 8 17.9967C8 17.4444 7.77614 16.9444 7.41421 16.5825ZM7.41421 7.41092C7.77614 7.04899 8 6.54899 8 5.9967C8 4.89213 7.10457 3.9967 6 3.9967C4.89543 3.9967 4 4.89213 4 5.9967C4 7.10127 4.89543 7.9967 6 7.9967C6.55228 7.9967 7.05228 7.77285 7.41421 7.41092Z"></path></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-search-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-skip-back-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M7 4C7.55228 4 8 4.44772 8 5V11.3333L18.2227 4.51823C18.4524 4.36506 18.7628 4.42714 18.916 4.65691C18.9708 4.73904 19 4.83555 19 4.93426V19.0657C19 19.3419 18.7761 19.5657 18.5 19.5657C18.4013 19.5657 18.3048 19.5365 18.2227 19.4818L8 12.6667V19C8 19.5523 7.55228 20 7 20C6.44772 20 6 19.5523 6 19V5C6 4.44772 6.44772 4 7 4ZM17 7.73703L10.6056 12L17 16.263V7.73703Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-skip-forward-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M16 12.6667L5.77735 19.4818C5.54759 19.6349 5.23715 19.5729 5.08397 19.3431C5.02922 19.261 5 19.1645 5 19.0657V4.93426C5 4.65812 5.22386 4.43426 5.5 4.43426C5.59871 4.43426 5.69522 4.46348 5.77735 4.51823L16 11.3333V5C16 4.44772 16.4477 4 17 4C17.5523 4 18 4.44772 18 5V19C18 19.5523 17.5523 20 17 20C16.4477 20 16 19.5523 16 19V12.6667ZM7 7.73703V16.263L13.3944 12L7 7.73703Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-thumb-down-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M9.40017 16H3C1.89543 16 1 15.1046 1 14V11.8957C1 11.6344 1.05118 11.3757 1.15064 11.1342L4.24501 3.61925C4.3993 3.24455 4.76447 3 5.16969 3H22C22.5523 3 23 3.44772 23 4V14C23 14.5523 22.5523 15 22 15H18.5182C18.1932 15 17.8886 15.1579 17.7012 15.4233L12.2478 23.149C12.1053 23.3508 11.8367 23.4184 11.6157 23.3078L9.80163 22.4008C8.74998 21.875 8.20687 20.6874 8.49694 19.548L9.40017 16ZM17 13.4125V5H5.83939L3 11.8957V14H9.40017C10.7049 14 11.6602 15.229 11.3384 16.4934L10.4351 20.0414C10.3771 20.2693 10.4857 20.5068 10.6961 20.612L11.3572 20.9425L16.0673 14.27C16.3172 13.9159 16.6366 13.6257 17 13.4125ZM19 13H21V5H19V13Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-thumb-up-line.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M14.5998 8.00033H21C22.1046 8.00033 23 8.89576 23 10.0003V12.1047C23 12.3659 22.9488 12.6246 22.8494 12.8662L19.755 20.3811C19.6007 20.7558 19.2355 21.0003 18.8303 21.0003H2C1.44772 21.0003 1 20.5526 1 20.0003V10.0003C1 9.44804 1.44772 9.00033 2 9.00033H5.48184C5.80677 9.00033 6.11143 8.84246 6.29881 8.57701L11.7522 0.851355C11.8947 0.649486 12.1633 0.581978 12.3843 0.692483L14.1984 1.59951C15.25 2.12534 15.7931 3.31292 15.5031 4.45235L14.5998 8.00033ZM7 10.5878V19.0003H18.1606L21 12.1047V10.0003H14.5998C13.2951 10.0003 12.3398 8.77128 12.6616 7.50691L13.5649 3.95894C13.6229 3.73105 13.5143 3.49353 13.3039 3.38837L12.6428 3.0578L7.93275 9.73038C7.68285 10.0844 7.36341 10.3746 7 10.5878ZM5 11.0003H3V19.0003H5V11.0003Z"/></svg>

+ 1 - 0
packages/stdf/src/assets/svgs/ri-truck-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8.96456 18C8.72194 19.6961 7.26324 21 5.5 21C3.73676 21 2.27806 19.6961 2.03544 18H1V6C1 5.44772 1.44772 5 2 5H16C16.5523 5 17 5.44772 17 6V8H20L23 12.0557V18H20.9646C20.7219 19.6961 19.2632 21 17.5 21C15.7368 21 14.2781 19.6961 14.0354 18H8.96456ZM15 7H3V15.0505C3.63526 14.4022 4.52066 14 5.5 14C6.8962 14 8.10145 14.8175 8.66318 16H14.3368C14.5045 15.647 14.7296 15.3264 15 15.0505V7ZM17 13H21V12.715L18.9917 10H17V13ZM17.5 19C18.1531 19 18.7087 18.5826 18.9146 18C18.9699 17.8436 19 17.6753 19 17.5C19 16.6716 18.3284 16 17.5 16C16.6716 16 16 16.6716 16 17.5C16 17.6753 16.0301 17.8436 16.0854 18C16.2913 18.5826 16.8469 19 17.5 19ZM7 17.5C7 16.6716 6.32843 16 5.5 16C4.67157 16 4 16.6716 4 17.5C4 17.6753 4.03008 17.8436 4.08535 18C4.29127 18.5826 4.84689 19 5.5 19C6.15311 19 6.70873 18.5826 6.91465 18C6.96992 17.8436 7 17.6753 7 17.5Z"></path></svg>

+ 306 - 117
packages/stdf/src/data/menuList.ts

@@ -11,8 +11,8 @@ export type MenuList = { class: string; class_en: string; childs: MenuListChild[
 
 export const menuList: MenuList[] = [
 	{
-		class: '通用',
-		class_en: 'Universal',
+		class: '基础',
+		class_en: 'Base',
 		childs: [
 			{
 				title: '按钮 Button',
@@ -21,17 +21,16 @@ export const menuList: MenuList[] = [
 				nav: 'button',
 				tip: '按下它,让世界变得更美好吧。',
 				tip_en: 'Press it and make the world a better place.',
-				alias: 'button|按钮'
+				alias: 'button | 按钮'
 			},
 			{
-				title: '底部浮窗 BottomSheet',
-				title_zh: '底部浮窗',
-				title_en: 'BottomSheet',
-				nav: 'bottomSheet',
-				tip: '浮窗犹如小偷,突然出现让你焦灼,却也能唤起你的警觉。',
-				tip_en: 'The floating window is like a thief, suddenly appearing to make you anxious, but it can also arouse your alertness.',
-				alias:
-					'bottomSheet|底部浮窗 | 底部 | 底|部 | 浮窗|bottomSheet 底部浮窗|bottomSheet 底部浮窗 | 底部弹窗|bottomSheet 底部弹窗|bottomSheet 底部弹窗 | 底部弹出框|bottomSheet 底部弹出框|bottomSheet 底部弹出框 | 底部弹出层|bottomSheet 底部弹出层|bottomSheet 底部弹出层'
+				title: '按钮组 ButtonGroup',
+				title_zh: '按钮组',
+				title_en: 'ButtonGroup',
+				nav: 'buttonGroup',
+				tip: '团结就是力量,按钮也不例外。',
+				tip_en: 'Unity is strength, buttons are no exception.',
+				alias: 'buttonGroup | 按钮组 | button group | 组合按钮'
 			},
 			{
 				title: '图标 Icon',
@@ -40,7 +39,22 @@ export const menuList: MenuList[] = [
 				nav: 'icon',
 				tip: '使用 SVG Sprites,请参照指南引入与使用。',
 				tip_en: 'Use SVG Sprites, please refer to the guide to import and use.',
-				alias: 'icon|图标|svg'
+				alias: 'icon | 图标 | svg'
+			}
+		]
+	},
+	{
+		class: '弹层基础',
+		class_en: 'Overlay Base',
+		childs: [
+			{
+				title: '底部浮窗 BottomSheet',
+				title_zh: '底部浮窗',
+				title_en: 'BottomSheet',
+				nav: 'bottomSheet',
+				tip: '浮窗犹如小偷,突然出现让你焦灼,却也能唤起你的警觉。',
+				tip_en: 'The floating window is like a thief, suddenly appearing to make you anxious, but it can also arouse your alertness.',
+				alias: 'bottomSheet | 底部浮窗 | 底部弹窗 | 底部弹出层 | 浮窗'
 			},
 			{
 				title: '遮罩 Mask',
@@ -49,7 +63,7 @@ export const menuList: MenuList[] = [
 				nav: 'mask',
 				tip: '遮罩虽能掩盖身形,却无法掩藏内心。',
 				tip_en: 'Although the mask can cover the figure, it cannot hide the heart.',
-				alias: 'mask|遮罩 | 遮|罩|mask 遮罩|mask 遮罩 | 遮罩层|mask 遮罩层|mask 遮罩层|Overlay|maskOverlay|mask Overlay'
+				alias: 'mask | 遮罩 | 遮罩层 | overlay'
 			},
 			{
 				title: '弹出层 Popup',
@@ -58,8 +72,31 @@ export const menuList: MenuList[] = [
 				nav: 'popup',
 				tip: '机会就像弹珠,只有你努力地推它,它才会弹出来。',
 				tip_en: 'Opportunity is like a marble, only if you push it hard, it will pop out.',
-				alias:
-					'popup|弹出层 | 弹出 | 弹|出 | 层|popup 弹出层|popup 弹出层 | 弹窗|popup 弹窗|popup 弹窗 | 弹出框|popup 弹出框|popup 弹出框 | 弹出层|popup 弹出层|popup 弹出层'
+				alias: 'popup | 弹出层 | 弹窗 | 弹出框'
+			}
+		]
+	},
+	{
+		class: '操作弹层',
+		class_en: 'Action Overlays',
+		childs: [
+			{
+				title: '操作气泡 ActionPopover',
+				title_zh: '操作气泡',
+				title_en: 'ActionPopover',
+				nav: 'actionPopover',
+				tip: '轻轻一点,动作尽在掌心。',
+				tip_en: 'A light tap, actions at your fingertips.',
+				alias: 'actionPopover | 操作气泡 | 动作气泡 | 动作 | 气泡 | 操作 | 菜单'
+			},
+			{
+				title: '操作面板 ActionSheet',
+				title_zh: '操作面板',
+				title_en: 'ActionSheet',
+				nav: 'actionSheet',
+				tip: '我的世界只有一个选项,就是你。',
+				tip_en: 'There is only one option in my world, and that is you.',
+				alias: 'actionSheet | 操作面板 | 动作面板 | 操作 | 动作 | 面板'
 			}
 		]
 	},
@@ -67,6 +104,15 @@ export const menuList: MenuList[] = [
 		class: '布局',
 		class_en: 'Layout',
 		childs: [
+			{
+				title: '卡片 Card',
+				title_zh: '卡片',
+				title_en: 'Card',
+				nav: 'card',
+				tip: '让内容有了归属,有了边界,也有了意义。',
+				tip_en: 'Give content a home, a boundary, and a meaning.',
+				alias: 'card | 卡片 | 容器 | container'
+			},
 			{
 				title: '分割线 Divider',
 				title_zh: '分割线',
@@ -74,7 +120,7 @@ export const menuList: MenuList[] = [
 				nav: 'divider',
 				tip: '分开的日子越久,最初的喜悦越发淡薄。',
 				tip_en: 'The longer the days are separated, the more the initial joy fades.',
-				alias: 'divider|分割线 | 分割|line'
+				alias: 'divider | 分割线 | 分割 | line'
 			},
 			{
 				title: '网格 Grids',
@@ -83,7 +129,7 @@ export const menuList: MenuList[] = [
 				nav: 'grids',
 				tip: '可是怎么说,总觉得,我们之间留了太多空白格。',
 				tip_en: 'How can I say, I always feel that there are too many blank grids between us.',
-				alias: 'grids|网格|grid|row|col|column'
+				alias: 'grids | 网格 | grid | row | col | column'
 			},
 			{
 				title: '占位符 Placeholder',
@@ -92,7 +138,7 @@ export const menuList: MenuList[] = [
 				nav: 'placeholder',
 				tip: '人生重要的不是所站的位置,而是所朝的方向。',
 				tip_en: 'What is important in life is not the position you stand, but the direction you face.',
-				alias: 'placeholder|占位符 | 占位'
+				alias: 'placeholder | 占位符 | 占位'
 			},
 			{
 				title: '骨架屏 Skeleton',
@@ -101,7 +147,7 @@ export const menuList: MenuList[] = [
 				nav: 'skeleton',
 				tip: '骨架是支撑身体的关键,而人生信念则是支撑生命的骨架。',
 				tip_en: 'The skeleton is the key to supporting the body, and the belief in life is the skeleton that supports life.',
-				alias: 'skeleton|骨架屏 | 骨架'
+				alias: 'skeleton | 骨架屏 | 骨架'
 			}
 		]
 	},
@@ -116,7 +162,7 @@ export const menuList: MenuList[] = [
 				nav: 'indexBar',
 				tip: '你不迷途知返,我怎么给你以索引?',
 				tip_en: "If you don't know the way back, how can I give you an index?",
-				alias: 'indexBar|索引栏|indexbar|index|bar|索引|index 栏 | 索引条|index |index 条'
+				alias: 'indexBar | 索引栏 | indexbar | index | bar | 索引 | 索引条'
 			},
 			{
 				title: '导航栏 NavBar',
@@ -125,7 +171,7 @@ export const menuList: MenuList[] = [
 				nav: 'navBar',
 				tip: '没有指引,在黑暗中只能乱撞。',
 				tip_en: 'Without guidance, you can only bump in the dark.',
-				alias: 'navBar|导航栏|navbar|nav|bar|导航|nav-bar|nav bar'
+				alias: 'navBar | 导航栏 | navbar | nav | bar | 导航 | nav-bar'
 			},
 			{
 				title: '分页 Pagination',
@@ -134,7 +180,7 @@ export const menuList: MenuList[] = [
 				nav: 'pagination',
 				tip: '人生就像一部电影,不论多么精彩,也有谢幕的时候。',
 				tip_en: 'Life is like a movie, no matter how wonderful it is, there is a curtain call.',
-				alias: 'pagination|分页|page|分 | 页|pagination 分页|pagination 分页|page 分页|page 分页'
+				alias: 'pagination | 分页 | page'
 			},
 			{
 				title: '步骤条 Steps',
@@ -143,7 +189,7 @@ export const menuList: MenuList[] = [
 				nav: 'steps',
 				tip: '人生没有白走的路,每一步都算数。',
 				tip_en: 'Life has no wasted steps, every step counts.',
-				alias: 'steps|step|步骤条 | 步骤|step |step 条 | 步骤栏|step 栏|step 栏'
+				alias: 'steps | step | 步骤条 | 步骤'
 			},
 			{
 				title: '标签栏 TabBar',
@@ -152,7 +198,7 @@ export const menuList: MenuList[] = [
 				nav: 'tabBar',
 				tip: '看脚下一片黑暗,望头顶星光璀璨。',
 				tip_en: "Looking down, it's all darkness; looking up, the stars are shining brightly.",
-				alias: 'tabBar|标签栏|tabbar|tab|bar|标签|tab 栏'
+				alias: 'tabBar | 标签栏 | tabbar | tab | bar | 标签'
 			},
 			{
 				title: '标签页 Tabs',
@@ -161,53 +207,22 @@ export const menuList: MenuList[] = [
 				nav: 'tabs',
 				tip: '偶尔隐藏,是为了在合适的时候更好地绽放。',
 				tip_en: 'Occasionally hide, it is to bloom better at the right time.',
-				alias: 'tabs|tab|标签页 | 标签|tab 页'
+				alias: 'tabs | tab | 标签页 | 标签'
 			}
 		]
 	},
 	{
-		class: '数据输入',
-		class_en: 'Data entry',
+		class: '表单与输入',
+		class_en: 'Form Input',
 		childs: [
 			{
-				title: '动作面板 ActionSheet',
-				title_zh: '动作面板',
-				title_en: 'ActionSheet',
-				nav: 'actionSheet',
-				tip: '我的世界只有一个选项,就是你。',
-				tip_en: 'There is only one option in my world, and that is you.',
-				alias:
-					'actionSheet|动作面板 | 动作 | 动|作 | 面板|actionSheet 动作面板|actionSheet 动作面板 | 动作面板|actionSheet 动作面板|actionSheet 动作面板 | 动作面板|actionSheet 动作面板|actionSheet 动作面板'
-			},
-			{
-				title: '异步选择器 AsyncPicker',
-				title_zh: '异步选择器',
-				title_en: 'AsyncPicker',
-				nav: 'asyncPicker',
-				tip: '错过的话,就请错过一辈子吧!',
-				tip_en: 'If you miss it, please miss it for a lifetime!',
-				alias:
-					'asyncPicker|异步选择器 | 异步|asyncPicker 异步选择器|asyncPicker 异步选择器|async picker|asyncpicker|异步 picker|异步 picker'
-			},
-			{
-				title: '日历 Calendar',
-				title_zh: '日历',
-				title_en: 'Calendar',
-				nav: 'calendar',
-				tip: '在无人问津日子里,正是登峰造极的好时机。',
-				tip_en: 'In the days when no one cares, it is a good time to reach the peak.',
-				alias:
-					'calendar|日历 | 日|历|calendar 日历|calendar 日历 | 日期|calendar 日期|calendar 日期 | 周选择|calendar 周选择|calendar 周选择 | 周|calendar 周|calendar 周 | 日期区间 | 月选择 | 日期范围选择'
-			},
-			{
-				title: '复选框 Checkbox',
-				title_zh: '复选框',
-				title_en: 'Checkbox',
-				nav: 'checkbox',
-				tip: '要么全不,要么全部。',
-				tip_en: 'Either all or none.',
-				alias:
-					'checkbox|复选框 | 复选|checkbox 复选框|checkbox 复选框 | 复选按钮|checkbox 复选按钮|checkbox 复选按钮 | 多选框 | 多选|checkbox 多选框|checkbox 多选框 | 多选按钮|checkbox 多选按钮|checkbox 多选按钮'
+				title: '码输入框 CodeInput',
+				title_zh: '码输入框',
+				title_en: 'CodeInput',
+				nav: 'codeInput',
+				tip: '每一次输入,都是通往正确答案的钥匙。',
+				tip_en: 'Every input is the key to the correct answer.',
+				alias: 'codeInput | 码输入框 | 验证码 | 密码输入 | password | code | 验证 | 密码 | 格子输入'
 			},
 			{
 				title: '表单 Form',
@@ -216,7 +231,16 @@ export const menuList: MenuList[] = [
 				nav: 'form',
 				tip: '别让用户思考,除非你想让他们逃跑。',
 				tip_en: 'Do not make users think, unless you want them to run away.',
-				alias: 'form|表单'
+				alias: 'form | 表单'
+			},
+			{
+				title: '全键盘 FullKeyboard',
+				title_zh: '全键盘',
+				title_en: 'FullKeyboard',
+				nav: 'fullKeyboard',
+				tip: 'QWERTY,打字机时代的遗产,至今仍在指尖流淌。',
+				tip_en: 'QWERTY, a legacy from the typewriter era, still flows at our fingertips.',
+				alias: 'fullKeyboard | 全键盘 | 字母键盘 | 键盘 | keyboard | letter | qwerty | abc'
 			},
 			{
 				title: '输入框 Input',
@@ -225,8 +249,7 @@ export const menuList: MenuList[] = [
 				nav: 'input',
 				tip: '对方正在输入......',
 				tip_en: 'The other party is typing...',
-				alias:
-					'input|输入框 | 输入|input 输入框|input 输入框|Textarea|textarea|文本域 | 文本框 | 文本|textarea 文本域|textarea 文本域 | 多行输入框 | 多行输入 | 多行|textarea 多行输入框|textarea 多行输入框'
+				alias: 'input | 输入框 | 输入 | textarea | 文本域 | 文本框 | 多行输入'
 			},
 			{
 				title: '数字键盘 NumKeyboard',
@@ -235,17 +258,31 @@ export const menuList: MenuList[] = [
 				nav: 'numKeyboard',
 				tip: '3 0624700 | 3 0624770 | 5 34202 13942 | 4314 0624',
 				tip_en: '3 0624700 | 3 0624770 | 5 34202 13942 | 4314 0624',
-				alias:
-					'numKeyboard|数字键盘 | 数字 | 键盘|numKeyboard 数字键盘|numKeyboard 数字键盘 | 金额键盘|numKeyboard 金额键盘|numKeyboard 金额键盘|number|keyboard|amount|money|'
+				alias: 'numKeyboard | 数字键盘 | 数字 | 键盘 | 金额键盘 | number | keyboard | amount | money'
 			},
 			{
-				title: '选择器 Picker',
-				title_zh: '选择器',
-				title_en: 'Picker',
-				nav: 'picker',
-				tip: '采摘花瓣时,你得不到花的美丽。',
-				tip_en: 'When you pick petals, you don’t get the beauty of the flower.',
-				alias: 'picker|选择器 | 选择|picker 选择器|picker 选择器'
+				title: '签名 Signature',
+				title_zh: '签名',
+				title_en: 'Signature',
+				nav: 'signature',
+				tip: '签名是灵魂的印记,笔下皆是承诺。',
+				tip_en: 'A signature is the mark of the soul, every stroke is a promise.',
+				alias: 'signature | 签名 | 手写 | 签字 | canvas | 画板 | 手写板'
+			}
+		]
+	},
+	{
+		class: '选择控件',
+		class_en: 'Selection Controls',
+		childs: [
+			{
+				title: '复选框 Checkbox',
+				title_zh: '复选框',
+				title_en: 'Checkbox',
+				nav: 'checkbox',
+				tip: '要么全不,要么全部。',
+				tip_en: 'Either all or none.',
+				alias: 'checkbox | 复选框 | 复选 | 多选框 | 多选'
 			},
 			{
 				title: '单选框 Radio',
@@ -254,7 +291,7 @@ export const menuList: MenuList[] = [
 				nav: 'radio',
 				tip: '如果要在正确和善良中选择一个,请选择善良。',
 				tip_en: 'If you have to choose between right and good, choose good.',
-				alias: 'radio|单选框 | 单选|radio 单选框|radio 单选框 | 单选按钮|radio 单选按钮|radio 单选按钮'
+				alias: 'radio | 单选框 | 单选 | 单选按钮'
 			},
 			{
 				title: '评分 Rate',
@@ -263,7 +300,7 @@ export const menuList: MenuList[] = [
 				nav: 'rate',
 				tip: '散场时,对方剩余的手机电量,就是这场约会的评分。',
 				tip_en: 'At the end of the game, the remaining battery power of the other party is the score of this date.',
-				alias: 'rate|评分 | 评|分|rate 评分|rate 评分'
+				alias: 'rate | 评分'
 			},
 			{
 				title: '滑块 Slider',
@@ -272,7 +309,17 @@ export const menuList: MenuList[] = [
 				nav: 'slider',
 				tip: '请滑动解锁您的烦恼。',
 				tip_en: 'Please slide to unlock your troubles.',
-				alias: 'slider|滑块 | 滑|块|slider 滑块|slider 滑块'
+				alias: 'slider | 滑块'
+			},
+			{
+				title: '步进器 Stepper',
+				title_zh: '步进器',
+				title_en: 'Stepper',
+				nav: 'stepper',
+				tip: '人生如同诗行,或进或退皆成章。保持心灵节奏,生命之诗韵味悠长。',
+				tip_en:
+					'Life is like a poem, advancing or retreating all form chapters. Maintain the rhythm of the soul, and the poetic flavor of life will last long.',
+				alias: 'stepper | 步进器'
 			},
 			{
 				title: '开关 Switch',
@@ -281,17 +328,49 @@ export const menuList: MenuList[] = [
 				nav: 'switch',
 				tip: '死亡是一个开关,活着却是一根绳索。',
 				tip_en: 'Death is a switch, but life is a rope.',
-				alias: 'switch|开关 | 开|关|switch 开关|switch 开关'
+				alias: 'switch | 开关'
+			}
+		]
+	},
+	{
+		class: '选择器',
+		class_en: 'Pickers',
+		childs: [
+			{
+				title: '异步选择器 AsyncPicker',
+				title_zh: '异步选择器',
+				title_en: 'AsyncPicker',
+				nav: 'asyncPicker',
+				tip: '错过的话,就请错过一辈子吧!',
+				tip_en: 'If you miss it, please miss it for a lifetime!',
+				alias: 'asyncPicker | 异步选择器 | 异步 | async'
 			},
 			{
-				title: '步进器 Stepper',
-				title_zh: '步进器',
-				title_en: 'Stepper',
-				nav: 'stepper',
-				tip: '人生如同诗行,或进或退皆成章。保持心灵节奏,生命之诗韵味悠长。',
-				tip_en:
-					'Life is like a poem, advancing or retreating all form chapters. Maintain the rhythm of the soul, and the poetic flavor of life will last long.',
-				alias: 'stepper|步进器 | 步|进 | 器|stepper 步进器|stepper 步进器'
+				title: '日历 Calendar',
+				title_zh: '日历',
+				title_en: 'Calendar',
+				nav: 'calendar',
+				tip: '在无人问津日子里,正是登峰造极的好时机。',
+				tip_en: 'In the days when no one cares, it is a good time to reach the peak.',
+				alias: 'calendar | 日历 | 日期 | 周选择 | 月选择 | 日期区间'
+			},
+			{
+				title: '颜色选择器 ColorPicker',
+				title_zh: '颜色选择器',
+				title_en: 'ColorPicker',
+				nav: 'colorPicker',
+				tip: '色彩是灵魂的语言,选择你想要表达的情绪。',
+				tip_en: 'Color is the language of the soul, choose the emotion you want to express.',
+				alias: 'colorPicker | 颜色选择器 | color | picker | 颜色 | 取色器 | 调色板 | palette'
+			},
+			{
+				title: '选择器 Picker',
+				title_zh: '选择器',
+				title_en: 'Picker',
+				nav: 'picker',
+				tip: '采摘花瓣时,你得不到花的美丽。',
+				tip_en: "When you pick petals, you don't get the beauty of the flower.",
+				alias: 'picker | 选择器 | 选择'
 			},
 			{
 				title: '时间选择器 TimePicker',
@@ -300,14 +379,46 @@ export const menuList: MenuList[] = [
 				nav: 'timePicker',
 				tip: '死亡不是失去生命,而是走出了时间的枷锁。',
 				tip_en: 'Death is not the loss of life, but the release of the shackles of time.',
-				alias:
-					'timePicker|时间选择器 | 时间|timePicker 时间选择器|timePicker 时间选择器|time picker|timepicker|时间 picker|时间 picker|日期选择器 | 日期|datePicker 日期选择器|datePicker 日期选择器|date picker|datepicker|日期 picker|日期 picker'
+				alias: 'timePicker | 时间选择器 | 时间 | 日期选择器 | 日期 | datePicker'
+			}
+		]
+	},
+	{
+		class: '列表与单元格',
+		class_en: 'Lists and Cells',
+		childs: [
+			{
+				title: '手风琴 Accordion',
+				title_zh: '手风琴',
+				title_en: 'Accordion',
+				nav: 'accordion',
+				tip: '折叠与展开之间,藏着无限可能。',
+				tip_en: 'Between folding and unfolding, there are infinite possibilities.',
+				alias: 'accordion | 手风琴 | 折叠面板 | collapse | expand | 折叠 | 展开'
+			},
+			{
+				title: '单元格 Cell',
+				title_zh: '单元格',
+				title_en: 'Cell',
+				nav: 'cell',
+				tip: '当全世界抛弃您的时候请记住,您身上有几亿细胞都在为你而活。',
+				tip_en: 'When the whole world abandons you, remember that there are billions of cells on your body that are living for you.',
+				alias: 'cell | 单元格'
+			},
+			{
+				title: '列表 List',
+				title_zh: '列表',
+				title_en: 'List',
+				nav: 'list',
+				tip: '生活就像一份清单,总有做不完的事。',
+				tip_en: 'Life is like a list, there is always something to do.',
+				alias: 'list | 列表 | 无限滚动 | 下拉刷新 | infinite | scroll | pull | refresh'
 			}
 		]
 	},
 	{
-		class: '信息展示',
-		class_en: 'Info display',
+		class: '数据展示',
+		class_en: 'Data Display',
 		childs: [
 			{
 				title: '头像 Avatar',
@@ -316,7 +427,16 @@ export const menuList: MenuList[] = [
 				nav: 'avatar',
 				tip: '其实我发给您的头像,是情侣头像...',
 				tip_en: 'Actually, the avatar I sent to you is a couple avatar...',
-				alias: 'avatar|头像 | 头|像|avatar 头像|avatar 头像'
+				alias: 'avatar | 头像'
+			},
+			{
+				title: '头像组 AvatarGroup',
+				title_zh: '头像组',
+				title_en: 'AvatarGroup',
+				nav: 'avatarGroup',
+				tip: '孤单是一个人的狂欢,狂欢是一群人的孤单。',
+				tip_en: "Loneliness is one person's revelry, revelry is a group's loneliness.",
+				alias: 'avatarGroup | 头像组 | avatar group | 头像列表 | avatars'
 			},
 			{
 				title: '徽标 Badge',
@@ -325,16 +445,25 @@ export const menuList: MenuList[] = [
 				nav: 'badge',
 				tip: '在标记消失之前,回到我身边。',
 				tip_en: 'Before the mark disappears, come back to me.',
-				alias: 'badge|徽标 | 徽||badge 徽标|badge 徽标 | 标记|badge 标记|badge 标记'
+				alias: 'badge | 徽标 | 标记'
 			},
 			{
-				title: '单元格 Cell',
-				title_zh: '单元格',
-				title_en: 'Cell',
-				nav: 'cell',
-				tip: '当全世界抛弃您的时候请记住,您身上有几亿细胞都在为你而活。',
-				tip_en: 'When the whole world abandons you, remember that there are billions of cells on your body that are living for you.',
-				alias: 'cell|单元格 | 单元|cell 单元格|cell 单元格'
+				title: '字符滚动 CharRoll',
+				title_zh: '字符滚动',
+				title_en: 'CharRoll',
+				nav: 'charRoll',
+				tip: '数字跳动,如同心跳,记录着每一次变化。',
+				tip_en: 'Numbers roll like heartbeats, recording every change.',
+				alias: 'charRoll | 字符滚动 | 数字滚动 | 数字动画 | number | roll | animation | 翻牌 | 老虎机'
+			},
+			{
+				title: '倒计时 CountDown',
+				title_zh: '倒计时',
+				title_en: 'CountDown',
+				nav: 'countDown',
+				tip: '时间不会等你,但倒计时会提醒你。',
+				tip_en: 'Time does not wait for you, but the countdown will remind you.',
+				alias: 'countDown | 倒计时 | countdown | timer | 计时器'
 			},
 			{
 				title: '通告栏 NoticeBar',
@@ -343,8 +472,7 @@ export const menuList: MenuList[] = [
 				nav: 'noticeBar',
 				tip: '我方开放地下城,这是告知,不是商量。',
 				tip_en: 'Our side opens the dungeon, this is to inform, not to discuss.',
-				alias:
-					'noticeBar|通告栏 | 通告 | 通|栏|noticeBar 通告栏|noticeBar 通告栏 | 通知栏|noticeBar 通知栏|noticeBar 通知栏 | 公告栏|noticeBar 公告栏|noticeBar 公告栏'
+				alias: 'noticeBar | 通告栏 | 通知栏 | 公告栏 | 通告'
 			},
 			{
 				title: '进度条 Progress',
@@ -353,7 +481,7 @@ export const menuList: MenuList[] = [
 				nav: 'progress',
 				tip: '好运进度:▓▓▓▓▓▓▓▓▓▓▓▓ 100%',
 				tip_en: 'Good luck progr: ▓▓▓▓▓▓▓▓▓▓▓▓ 100%',
-				alias: 'progress|进度条 | 进度 | 进|度|progress 进度条|progress 进度'
+				alias: 'progress | 进度条 | 进度'
 			},
 			{
 				title: '进度环 ProgressLoop',
@@ -362,8 +490,49 @@ export const menuList: MenuList[] = [
 				nav: 'progressLoop',
 				tip: '人生不是一个完美的圆环,一旦完美,你就再没有任何可能性。',
 				tip_en: 'Life is not a perfect circle. Once perfect, you have no possibility.',
-				alias:
-					'progressLoop|进度环 | 进度 | 进|度|progressLoop 进度环|progressLoop 进度环 | 环形进度条|progressLoop 环形进度条|progressLoop 环形进度条|progress loop|progress-loop'
+				alias: 'progressLoop | 进度环 | 环形进度条'
+			},
+			{
+				title: '标签 Tag',
+				title_zh: '标签',
+				title_en: 'Tag',
+				nav: 'tag',
+				tip: '标签是分类的艺术,让信息一目了然。',
+				tip_en: 'Tags are the art of classification, making information clear at a glance.',
+				alias: 'tag | 标签 | 标记 | 分类 | label'
+			},
+			{
+				title: '文字提示 Tooltip',
+				title_zh: '文字提示',
+				title_en: 'Tooltip',
+				nav: 'tooltip',
+				tip: '点击显示的小贴士,让操作更加明确。',
+				tip_en: 'A small tip that appears on click, making operations clearer.',
+				alias: 'tooltip | 文字提示 | 提示 | tip | 气泡 | popover | 弹出提示'
+			}
+		]
+	},
+	{
+		class: '媒体',
+		class_en: 'Media',
+		childs: [
+			{
+				title: '图片预览 ImagePreview',
+				title_zh: '图片预览',
+				title_en: 'ImagePreview',
+				nav: 'imagePreview',
+				tip: '全屏之下,细节无处遁形。',
+				tip_en: 'Under the full screen, details have nowhere to hide.',
+				alias: 'imagePreview | 图片预览 | 图片查看 | 预览 | preview | image | gallery | 相册'
+			},
+			{
+				title: '图片列表 ImageList',
+				title_zh: '图片列表',
+				title_en: 'ImageList',
+				nav: 'imageList',
+				tip: '展示图片,让世界看见你的视角。',
+				tip_en: 'Display images, let the world see your perspective.',
+				alias: 'imageList | 图片列表 | 图片展示 | 图片 | image | list | gallery'
 			},
 			{
 				title: '轮播 Swiper',
@@ -372,8 +541,7 @@ export const menuList: MenuList[] = [
 				nav: 'swiper',
 				tip: '人世间悲喜烂剧,昼夜轮播不停。',
 				tip_en: 'The ups and downs of life, day and night, are not stopping.',
-				alias:
-					'swiper|轮播 | 轮|播|swiper 轮播|swiper 轮播 | 轮播图|swiper 轮播图|swiper 轮播图 | 走马灯|swiper 走马灯|swiper 走马灯|carousel'
+				alias: 'swiper | 轮播 | 轮播图 | 走马灯 | carousel'
 			}
 		]
 	},
@@ -381,15 +549,23 @@ export const menuList: MenuList[] = [
 		class: '反馈',
 		class_en: 'Feedback',
 		childs: [
+			{
+				title: '弹窗提示 Alert',
+				title_zh: '弹窗提示',
+				title_en: 'Alert',
+				nav: 'alert',
+				tip: '重要的事情,值得用卡片来强调。',
+				tip_en: 'Important things deserve to be emphasized with a card.',
+				alias: 'alert | 弹窗提示 | 提示 | 通知 | notification | 卡片提示'
+			},
 			{
 				title: '对话框 Dialog',
 				title_zh: '对话框',
 				title_en: 'Dialog',
 				nav: 'dialog',
-				tip: '无法确定任何事情,尤其是按下“确定”键的那一刻,我最不确定。',
+				tip: '无法确定任何事情,尤其是按下"确定"键的那一刻,我最不确定。',
 				tip_en: 'Nothing can be determined, especially at the moment when the "OK" button is pressed, I am the most uncertain.',
-				alias:
-					'dialog|对话框 | 对话 | 对|话 | 框|dialog 对话框|dialog 对话框 | 对话框|dialog 对话框|dialog 对话框 | 对话框|dialog 对话框|dialog 对话框 | 对话框|dialog 对话框|dialog 对话框'
+				alias: 'dialog | 对话框 | 对话'
 			},
 			{
 				title: '加载 Loading',
@@ -398,7 +574,7 @@ export const menuList: MenuList[] = [
 				nav: 'loading',
 				tip: '不要再苦苦等待答案了,没有回复已是答案。',
 				tip_en: "Don't wait for the answer, no reply is the answer.",
-				alias: 'loading|加载 | 加载中|loading 加载|loading 加载|loading 加载中|loading 加载中|loading 加载中...|loading 加载中...'
+				alias: 'loading | 加载 | 加载中'
 			},
 			{
 				title: '弹框 Modal',
@@ -407,8 +583,7 @@ export const menuList: MenuList[] = [
 				nav: 'modal',
 				tip: '弹框即人生,要么选择关闭,要么选择继续。',
 				tip_en: 'The modal is life, either choose to close or choose to continue.',
-				alias:
-					'modal|弹框 | 弹|框|modal 弹框|modal 弹框 | 弹窗|modal 弹窗|modal 弹窗 | 弹出框|modal 弹出框|modal 弹出框 | 弹框|modal 弹框|modal 弹框 | 模态框|modal 模态框|modal 模态框'
+				alias: 'modal | 弹框 | 弹窗 | 弹出框 | 模态框'
 			},
 			{
 				title: '轻提示 Toast',
@@ -417,8 +592,22 @@ export const menuList: MenuList[] = [
 				nav: 'toast',
 				tip: '总有一些东西,要用消失来证明它的珍贵。',
 				tip_en: 'There are always some things that have to disappear to prove their value.',
-				alias:
-					'toast|轻提示 | 轻|提示|toast 轻提示|toast 轻提示 | 轻弹窗|toast 轻弹窗|toast 轻弹窗 | 轻提示框|toast 轻提示框|toast 轻提示框 | 吐司|toast 吐司|toast 吐司'
+				alias: 'toast | 轻提示 | 提示 | 吐司'
+			}
+		]
+	},
+	{
+		class: '函数式',
+		class_en: 'Functional',
+		childs: [
+			{
+				title: '函数式反馈 Feedback',
+				title_zh: '函数式反馈',
+				title_en: 'Feedback',
+				nav: 'feedback',
+				tip: '在任何地方调用反馈组件,无需在模板中声明。',
+				tip_en: 'Call feedback components anywhere, no need to declare in template.',
+				alias: 'feedback | 函数式反馈 | 函数式 | toast | dialog | modal | alert | loading'
 			}
 		]
 	}

+ 0 - 59
packages/stdf/src/data/themes/aqua.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Aqua: ThemeProps = {
-	name: 'Aqua',
-	color: {
-		primary: {
-			50: 'oklch(0.987 0.008 225.079)', // #F5FCFF hsl(198, 100%, 98%)
-			100: 'oklch(0.969 0.019 225.712)', // #E8F8FF hsl(198, 100%, 96%)
-			200: 'oklch(0.832 0.048 227.654)', // #A8CFE1 hsl(199, 49%, 77%)
-			300: 'oklch(0.699 0.07 233.534)', // #72A6C3 hsl(202, 40%, 61%)
-			400: 'oklch(0.575 0.083 237.386)', // #4580A4 hsl(203, 41%, 46%)
-			500: 'oklch(0.46 0.09 243.106)', // #235D86 hsl(205, 59%, 33%)
-			default: 'oklch(0.355 0.089 248.677)', // #0A3E68 hsl(207, 83%, 22%)
-			700: 'oklch(0.329 0.09 251.443)', // #073661 hsl(209, 87%, 20%)
-			800: 'oklch(0.302 0.09 253.945)', // #042E5A hsl(211, 92%, 18%)
-			900: 'oklch(0.277 0.09 255.696)', // #022753 hsl(213, 95%, 17%)
-			950: 'oklch(0.253 0.091 257.423)' // #00204D hsl(215, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.997 0.013 111.26)', // #FEFFF5 hsl(66, 100%, 98%)
-			100: 'oklch(0.992 0.03 110.976)', // #FDFFE8 hsl(65, 100%, 96%)
-			200: 'oklch(0.946 0.068 113.937)', // #ECF3BF hsl(68, 68%, 85%)
-			300: 'oklch(0.9 0.101 116.179)', // #DAE799 hsl(70, 62%, 75%)
-			400: 'oklch(0.857 0.13 118.796)', // #C7DC76 hsl(72, 59%, 66%)
-			500: 'oklch(0.812 0.152 121.068)', // #B3D055 hsl(74, 57%, 58%)
-			default: 'oklch(0.767 0.167 123.364)', // #9FC438 hsl(76, 56%, 49%)
-			700: 'oklch(0.673 0.157 125.534)', // #7FA624 hsl(78, 64%, 40%)
-			800: 'oklch(0.577 0.143 127.636)', // #618813 hsl(80, 76%, 30%)
-			900: 'oklch(0.479 0.124 129.495)', // #466A08 hsl(82, 86%, 22%)
-			950: 'oklch(0.381 0.104 130.976)' // #2E4D00 hsl(84, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.156 0.033 240.386)', // #010E19 hsl(207, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(207, 5%, 95%)
-		darkBlack: 'oklch(0.199 0.046 122.93)', // #121901 hsl(76, 95%, 5%)
-		darkWhite: 'oklch(0.964 0.001 106.424)', // #F3F3F2 hsl(76, 5%, 95%)
-		functional: {
-			success: 'oklch(0.577 0.18 144.916)', // #069327 hsl(134, 92%, 30%)
-			warning: 'oklch(0.615 0.147 54.981)', // #c66810 hsl(29, 85%, 42%)
-			error: 'oklch(0.559 0.203 26.879)', // #d12929 hsl(0, 67%, 49%)
-			info: 'oklch(0.455 0.194 263.236)' // #1649c0 hsl(222, 79%, 42%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.345 0.194 290.65)', // #410093 hsl(267, 100%, 29%)
-				alias: 'Yahoo!'
-			},
-			{
-				color: 'oklch(0.651 0.212 142.608)', // oklch(0.651 0.212 142.608) hsl(120, 75%, 39%)
-				alias: 'WeChat'
-			},
-			{
-				color: 'oklch(0.689 0.196 21.407)', // #fd5c63 hsl(357, 98%, 68%)
-				alias: 'Airbnb'
-			}
-		]
-	}
-};
-
-export default Aqua;

+ 0 - 59
packages/stdf/src/data/themes/azuper.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Azuper: ThemeProps = {
-	name: 'Azuper',
-	color: {
-		primary: {
-			50: 'oklch(0.987 0.008 225.079)', // #F5FCFF hsl(198, 100%, 98%)
-			100: 'oklch(0.967 0.019 230.689)', // #E8F7FF hsl(201, 100%, 96%)
-			200: 'oklch(0.827 0.048 234.484)', // #A9CCE1 hsl(203, 48%, 77%)
-			300: 'oklch(0.692 0.069 240.216)', // #74A2C3 hsl(205, 40%, 61%)
-			400: 'oklch(0.565 0.085 244.974)', // #487BA5 hsl(207, 39%, 47%)
-			500: 'oklch(0.45 0.094 249.497)', // #265887 hsl(209, 56%, 34%)
-			default: 'oklch(0.347 0.095 253.302)', // #0D3A69 hsl(211, 78%, 23%)
-			700: 'oklch(0.317 0.097 256.274)', // #093162 hsl(213, 83%, 21%)
-			800: 'oklch(0.291 0.098 258.443)', // #06295B hsl(215, 88%, 19%)
-			900: 'oklch(0.266 0.098 259.566)', // #032254 hsl(217, 93%, 17%)
-			950: 'oklch(0.241 0.099 260.073)' // #001B4D hsl(219, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.984 0.009 44.894)', // #FFF8F5 hsl(18, 100%, 98%)
-			100: 'oklch(0.963 0.02 45.262)', // #FFEFE8 hsl(18, 100%, 96%)
-			200: 'oklch(0.889 0.047 41.262)', // #F7D1C3 hsl(16, 77%, 87%)
-			300: 'oklch(0.817 0.075 38.298)', // #EFB3A0 hsl(14, 71%, 78%)
-			400: 'oklch(0.747 0.107 34.759)', // #E8947F hsl(12, 70%, 70%)
-			500: 'oklch(0.68 0.138 32.855)', // #E0755F hsl(10, 68%, 63%)
-			default: 'oklch(0.621 0.167 31.47)', // #D85742 hsl(8, 66%, 55%)
-			700: 'oklch(0.522 0.165 29.582)', // #B5372A hsl(6, 62%, 44%)
-			800: 'oklch(0.433 0.152 29.344)', // #921F16 hsl(4, 74%, 33%)
-			900: 'oklch(0.348 0.132 28.566)', // #6F0C09 hsl(2, 85%, 24%)
-			950: 'oklch(0.264 0.108 29.234)' // #4D0000 hsl(0, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.15 0.035 247.014)', // #010C19 hsl(211, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(211, 5%, 95%)
-		darkBlack: 'oklch(0.148 0.043 37.362)', // #190401 hsl(8, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(8, 5%, 95%)
-		functional: {
-			success: 'oklch(0.663 0.189 146.376)', // #21b043 hsl(134, 68%, 41%)
-			warning: 'oklch(0.652 0.163 41.893)', // #df6734 hsl(18, 73%, 54%)
-			error: 'oklch(0.616 0.196 13.669)', // #e14161 hsl(348, 73%, 57%)
-			info: 'oklch(0.523 0.229 273.504)' // #484eea hsl(238, 79%, 60%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.492 0.217 261.351)', // oklch(0.492 0.217 261.351) hsl(217, 100%, 43%)
-				alias: 'Tencent'
-			},
-			{
-				color: 'oklch(0.654 0.154 241.655)', // #0099e5 hsl(200, 100%, 45%)
-				alias: '500px'
-			},
-			{
-				color: 'oklch(0.571 0.235 261.561)', // #1769ff hsl(219, 100%, 55%)
-				alias: 'Behance'
-			}
-		]
-	}
-};
-
-export default Azuper;

+ 0 - 59
packages/stdf/src/data/themes/azurePink.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const AzurePink: ThemeProps = {
-	name: 'AzurePink',
-	color: {
-		primary: {
-			50: 'oklch(0.977 0.011 274.897)', // #F5F7FF hsl(228, 100%, 98%)
-			100: 'oklch(0.948 0.025 273.503)', // #E8EDFF hsl(227, 100%, 96%)
-			200: 'oklch(0.799 0.073 275.979)', // #B0BAED hsl(230, 63%, 81%)
-			300: 'oklch(0.66 0.12 275.608)', // #7E8BDB hsl(232, 56%, 68%)
-			400: 'oklch(0.531 0.164 274.903)', // #535EC9 hsl(234, 52%, 56%)
-			500: 'oklch(0.421 0.199 271.236)', // #2D36B7 hsl(236, 61%, 45%)
-			default: 'oklch(0.341 0.216 266.286)', // #0D12A5 hsl(238, 85%, 35%)
-			700: 'oklch(0.302 0.198 265.961)', // #08088F hsl(240, 89%, 30%)
-			800: 'oklch(0.269 0.175 267.031)', // #090579 hsl(242, 92%, 25%)
-			900: 'oklch(0.233 0.152 268.05)', // #080263 hsl(244, 96%, 20%)
-			950: 'oklch(0.196 0.128 270.599)' // #08004D hsl(246, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.979 0.011 356.992)', // #FFF5F8 hsl(342, 100%, 98%)
-			100: 'oklch(0.952 0.027 354.511)', // #FFE8F0 hsl(339, 100%, 96%)
-			200: 'oklch(0.863 0.076 354.34)', // #FBBED5 hsl(337, 88%, 87%)
-			300: 'oklch(0.783 0.127 353.824)', // #F795BE hsl(335, 86%, 78%)
-			400: 'oklch(0.713 0.175 354.443)', // #F36DA9 hsl(333, 85%, 69%)
-			500: 'oklch(0.658 0.216 355.121)', // #EF4698 hsl(331, 84%, 61%)
-			default: 'oklch(0.621 0.239 357.218)', // #EB2188 hsl(329, 84%, 53%)
-			700: 'oklch(0.54 0.212 354.913)', // #C31575 hsl(327, 81%, 42%)
-			800: 'oklch(0.458 0.183 353.544)', // #9C0B5F hsl(325, 87%, 33%)
-			900: 'oklch(0.37 0.15 351.13)', // #740449 hsl(323, 93%, 24%)
-			950: 'oklch(0.278 0.117 348.207)' // #4D0032 hsl(321, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.106 0.061 269.597)', // #010119 hsl(238, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(238, 5%, 95%)
-		darkBlack: 'oklch(0.144 0.055 350.158)', // #19010D hsl(329, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(329, 5%, 95%)
-		functional: {
-			success: 'oklch(0.629 0.199 141.373)', // #2ba417 hsl(112, 75%, 37%)
-			warning: 'oklch(0.674 0.166 52.005)', // #e37318 hsl(27, 81%, 49%)
-			error: 'oklch(0.599 0.177 27.079)', // #d54941 hsl(3, 64%, 55%)
-			info: 'oklch(0.492 0.217 261.351)' // #0052d9 hsl(217, 100%, 43%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.364 0.191 266.82)', // #1428a0 hsl(231, 78%, 35%)
-				alias: 'Samsung'
-			},
-			{
-				color: 'oklch(0.66 0.225 36.296)', // #fd4900 hsl(17, 100%, 50%)
-				alias: 'Xiaomi'
-			},
-			{
-				color: 'oklch(0.461 0.116 153.579)', // #046a38 hsl(151, 93%, 22%)
-				alias: 'OPPO'
-			}
-		]
-	}
-};
-
-export default AzurePink;

+ 0 - 59
packages/stdf/src/data/themes/camelliaRed.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const CamelliaRed: ThemeProps = {
-	name: 'CamelliaRed',
-	color: {
-		primary: {
-			50: 'oklch(0.98 0.01 25.073)', // #FFF6F5 hsl(6, 100%, 98%)
-			100: 'oklch(0.953 0.023 24.126)', // #FFEAE8 hsl(5, 100%, 96%)
-			200: 'oklch(0.876 0.061 22.144)', // #FCC7C4 hsl(3, 90%, 88%)
-			300: 'oklch(0.799 0.101 20.296)', // #F8A3A2 hsl(1, 86%, 80%)
-			400: 'oklch(0.732 0.143 20.141)', // #F58082 hsl(359, 85%, 73%)
-			500: 'oklch(0.675 0.18 20.489)', // #F15F66 hsl(357, 84%, 66%)
-			default: 'oklch(0.632 0.21 21.584)', // #EE3F4D hsl(355, 84%, 59%)
-			700: 'oklch(0.54 0.193 20.999)', // #C6273A hsl(353, 67%, 47%)
-			800: 'oklch(0.449 0.168 20.933)', // #9D1529 hsl(351, 76%, 35%)
-			900: 'oklch(0.36 0.138 20.161)', // #75081C hsl(349, 87%, 25%)
-			950: 'oklch(0.266 0.107 17.45)' // #4D0011 hsl(347, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.99 0.008 319.447)', // #FEFAFF hsl(288, 100%, 99%)
-			100: 'oklch(0.953 0.037 320.346)', // #FBE8FF hsl(290, 100%, 96%)
-			200: 'oklch(0.915 0.065 318.761)', // #F6D6FF hsl(287, 100%, 92%)
-			300: 'oklch(0.877 0.092 317.423)', // #F0C4FF hsl(285, 100%, 88%)
-			400: 'oklch(0.841 0.118 315.933)', // #E9B3FF hsl(283, 100%, 85%)
-			500: 'oklch(0.803 0.145 314.514)', // #E1A1FF hsl(281, 100%, 82%)
-			default: 'oklch(0.765 0.171 312.957)', // #D88FFF hsl(279, 100%, 78%)
-			700: 'oklch(0.602 0.188 310.729)', // #A458D2 hsl(277, 58%, 58%)
-			800: 'oklch(0.461 0.184 307.198)', // #742EA6 hsl(275, 57%, 42%)
-			900: 'oklch(0.339 0.159 303.074)', // #4A1179 hsl(273, 75%, 27%)
-			950: 'oklch(0.231 0.124 299.616)' // #28004D hsl(271, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.139 0.05 18.003)', // #190103 hsl(355, 95%, 5%)
-		primaryWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(355, 5%, 95%)
-		darkBlack: 'oklch(0.134 0.06 312.473)', // #100119 hsl(279, 95%, 5%)
-		darkWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(279, 5%, 95%)
-		functional: {
-			success: 'oklch(0.726 0.218 138.487)', // #52c41a hsl(100, 77%, 44%)
-			warning: 'oklch(0.802 0.165 76.001)', // #faad14 hsl(40, 96%, 53%)
-			error: 'oklch(0.673 0.214 24.465)', // #ff4d4f hsl(359, 100%, 65%)
-			info: 'oklch(0.599 0.219 259.04)' // #1677ff hsl(215, 100%, 54%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.472 0.108 263.58)', // #3b5998 hsl(221, 44%, 41%)
-				alias: 'Facebook'
-			},
-			{
-				color: 'oklch(0.602 0.236 28.928)', // #ed1c16 hsl(2, 86%, 51%)
-				alias: 'CocaCola'
-			},
-			{
-				color: 'oklch(0.51 0.145 250.251)', // #0068b5 hsl(206, 100%, 36%)
-				alias: 'Intel'
-			}
-		]
-	}
-};
-
-export default CamelliaRed;

+ 0 - 59
packages/stdf/src/data/themes/chameleon.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Chameleon: ThemeProps = {
-	name: 'Chameleon',
-	color: {
-		primary: {
-			50: 'oklch(0.991 0.014 155.599)', // #F5FFF8 hsl(138, 100%, 98%)
-			100: 'oklch(0.979 0.032 155.407)', // #E8FFEF hsl(138, 100%, 96%)
-			200: 'oklch(0.89 0.085 155.743)', // #ADECC3 hsl(141, 62%, 80%)
-			300: 'oklch(0.812 0.128 154.98)', // #78DA9D hsl(143, 57%, 66%)
-			400: 'oklch(0.742 0.153 154.251)', // #4AC77E hsl(145, 53%, 54%)
-			500: 'oklch(0.682 0.167 152.73)', // #23B564 hsl(147, 68%, 42%)
-			default: 'oklch(0.623 0.168 150.91)', // #02A24E hsl(149, 98%, 32%)
-			700: 'oklch(0.564 0.146 152.693)', // #018D49 hsl(151, 99%, 28%)
-			800: 'oklch(0.5 0.124 154.785)', // #017742 hsl(153, 98%, 24%)
-			900: 'oklch(0.437 0.104 156.756)', // #006239 hsl(155, 100%, 19%)
-			950: 'oklch(0.371 0.085 159.162)' // #004D2F hsl(157, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.98 0.014 336.137)', // #FFF5FC hsl(318, 100%, 98%)
-			100: 'oklch(0.955 0.034 333.19)', // #FFE8FA hsl(313, 100%, 96%)
-			200: 'oklch(0.896 0.07 332.956)', // #FACCF1 hsl(312, 82%, 89%)
-			300: 'oklch(0.842 0.108 332.745)', // #F6B1EA hsl(310, 79%, 83%)
-			400: 'oklch(0.793 0.145 331.761)', // #F197E5 hsl(308, 76%, 77%)
-			500: 'oklch(0.75 0.181 330.986)', // #ED7EE2 hsl(306, 76%, 71%)
-			default: 'oklch(0.711 0.213 330.284)', // #E866DF hsl(304, 74%, 66%)
-			700: 'oklch(0.6 0.215 329.149)', // #C140BD hsl(302, 51%, 50%)
-			800: 'oklch(0.496 0.202 328.098)', // #9A229A hsl(300, 64%, 37%)
-			900: 'oklch(0.391 0.17 326.834)', // #700D73 hsl(298, 80%, 25%)
-			950: 'oklch(0.284 0.133 324.454)' // #47004D hsl(295, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.189 0.042 157.928)', // #01190C hsl(149, 95%, 5%)
-		primaryWhite: 'oklch(0.963 0.002 145.562)', // #F2F3F2 hsl(149, 5%, 95%)
-		darkBlack: 'oklch(0.151 0.063 331.348)', // #190117 hsl(304, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.002 325.59)', // #F3F2F3 hsl(304, 5%, 95%)
-		functional: {
-			success: 'oklch(0.731 0.191 137.226)', // #67c23a hsl(100, 54%, 49%)
-			warning: 'oklch(0.761 0.139 73.316)', // #e6a23c hsl(36, 77%, 57%)
-			error: 'oklch(0.699 0.169 22.456)', // #f56c6c hsl(0, 87%, 69%)
-			info: 'oklch(0.663 0.01 264.502)' // #909399 hsl(220, 4%, 58%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.746 0.16 232.661)', // #00bcff hsl(194, 100%, 44%)
-				alias: 'Tailwind'
-			},
-			{
-				color: 'oklch(0.515 0.206 28.695)', // #c20c0c hsl(0, 88%, 40%)
-				alias: 'NetEaseCloudMusic'
-			},
-			{
-				color: 'oklch(0.566 0.238 268.917)', // #415fff hsl(231, 100%, 63%)
-				alias: 'vivo'
-			}
-		]
-	}
-};
-
-export default Chameleon;

+ 0 - 59
packages/stdf/src/data/themes/citrus.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Citrus: ThemeProps = {
-	name: 'Citrus',
-	color: {
-		primary: {
-			50: 'oklch(0.985 0.008 236.556)', // #F5FBFF hsl(204, 100%, 98%)
-			100: 'oklch(0.963 0.019 240.727)', // #E8F5FF hsl(206, 100%, 96%)
-			200: 'oklch(0.818 0.048 241.473)', // #A9C8E0 hsl(206, 47%, 77%)
-			300: 'oklch(0.677 0.07 246.931)', // #749CC1 hsl(209, 38%, 61%)
-			400: 'oklch(0.548 0.088 251.698)', // #4974A3 hsl(211, 38%, 46%)
-			500: 'oklch(0.431 0.097 254.641)', // #275184 hsl(213, 54%, 34%)
-			default: 'oklch(0.324 0.099 258.036)', // #0E3265 hsl(215, 76%, 23%)
-			700: 'oklch(0.298 0.102 260.178)', // #0A2A5F hsl(217, 81%, 21%)
-			800: 'oklch(0.274 0.103 261.304)', // #062359 hsl(219, 87%, 19%)
-			900: 'oklch(0.251 0.105 262.192)', // #031C53 hsl(221, 93%, 17%)
-			950: 'oklch(0.229 0.106 261.947)' // #00164D hsl(223, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.997 0.013 106.635)', // #FFFFF5 hsl(60, 100%, 98%)
-			100: 'oklch(0.994 0.03 106.933)', // #FFFFE8 hsl(60, 100%, 96%)
-			200: 'oklch(0.967 0.08 105.52)', // #FBF8B9 hsl(57, 89%, 86%)
-			300: 'oklch(0.937 0.122 105)', // #F6EF8B hsl(56, 86%, 76%)
-			400: 'oklch(0.903 0.152 102.892)', // #F2E35E hsl(54, 85%, 66%)
-			500: 'oklch(0.867 0.168 100.267)', // #EDD533 hsl(52, 84%, 57%)
-			default: 'oklch(0.826 0.168 94.589)', // #E9C30A hsl(50, 92%, 48%)
-			700: 'oklch(0.707 0.144 91.085)', // #C29C06 hsl(48, 94%, 39%)
-			800: 'oklch(0.588 0.12 87.218)', // #9B7703 hsl(46, 96%, 31%)
-			900: 'oklch(0.47 0.096 83.886)', // #745501 hsl(44, 98%, 23%)
-			950: 'oklch(0.35 0.072 82.069)' // #4D3600 hsl(42, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.147 0.037 249.929)', // #010B19 hsl(215, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(215, 5%, 95%)
-		darkBlack: 'oklch(0.195 0.038 99.68)', // #191501 hsl(50, 95%, 5%)
-		darkWhite: 'oklch(0.964 0.001 106.424)', // #F3F3F2 hsl(50, 5%, 95%)
-		functional: {
-			success: 'oklch(0.637 0.184 146.403)', // #1ba73e hsl(135, 72%, 38%)
-			warning: 'oklch(0.495 0.128 47.783)', // #9a460e hsl(24, 83%, 33%)
-			error: 'oklch(0.504 0.203 28.76)', // #bd0a0a hsl(0, 90%, 39%)
-			info: 'oklch(0.466 0.24 284.096)' // #5628d7 hsl(256, 69%, 50%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.703 0.149 235.113)', // oklch(0.703 0.149 235.059) hsl(197, 98%, 47%)
-				alias: 'Twitter'
-			},
-			{
-				color: 'oklch(0.702 0.194 38.137)', // oklch(0.702 0.194 38.137) hsl(15, 100%, 61%)
-				alias: 'Svelte'
-			},
-			{
-				color: 'oklch(0.716 0.171 53.446)', // #f38020 hsl(27, 90%, 54%)
-				alias: 'Cloudflare'
-			}
-		]
-	}
-};
-
-export default Citrus;

+ 0 - 59
packages/stdf/src/data/themes/greenJade.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const GreenJade: ThemeProps = {
-	name: 'GreenJade',
-	color: {
-		primary: {
-			50: 'oklch(0.992 0.011 176.326)', // #F5FFFC hsl(162, 100%, 98%)
-			100: 'oklch(0.982 0.025 181.302)', // #E8FFFA hsl(167, 100%, 96%)
-			200: 'oklch(0.871 0.05 182.107)', // #B1E0D7 hsl(169, 43%, 79%)
-			300: 'oklch(0.765 0.067 183.674)', // #81C1B7 hsl(171, 34%, 63%)
-			400: 'oklch(0.662 0.075 185.315)', // #59A299 hsl(173, 29%, 49%)
-			500: 'oklch(0.561 0.075 188.146)', // #38837D hsl(175, 40%, 37%)
-			default: 'oklch(0.46 0.067 190.938)', // #1F6461 hsl(177, 53%, 26%)
-			700: 'oklch(0.44 0.068 193.584)', // #165E5D hsl(179, 62%, 23%)
-			800: 'oklch(0.416 0.066 196.348)', // #0E5758 hsl(181, 73%, 20%)
-			900: 'oklch(0.389 0.064 199.408)', // #064F52 hsl(182, 86%, 17%)
-			950: 'oklch(0.36 0.062 206.153)' // #00464D hsl(186, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.993 0.011 95.159)', // #FFFDF5 hsl(48, 100%, 98%)
-			100: 'oklch(0.984 0.024 94.062)', // #FFFAE8 hsl(47, 100%, 96%)
-			200: 'oklch(0.963 0.055 93.624)', // #FFF3CA hsl(46, 100%, 90%)
-			300: 'oklch(0.938 0.081 90.541)', // #FFE9AC hsl(44, 100%, 84%)
-			400: 'oklch(0.909 0.106 87.283)', // #FFDD8D hsl(42, 100%, 78%)
-			500: 'oklch(0.877 0.126 83.083)', // #FFCF6F hsl(40, 100%, 72%)
-			default: 'oklch(0.844 0.144 77.934)', // #FFBF51 hsl(38, 100%, 66%)
-			700: 'oklch(0.707 0.132 72.908)', // #D29232 hsl(36, 64%, 51%)
-			800: 'oklch(0.574 0.117 66.999)', // #A6691A hsl(34, 73%, 38%)
-			900: 'oklch(0.444 0.097 61.688)', // #79450A hsl(32, 85%, 26%)
-			950: 'oklch(0.315 0.075 58.16)' // #4D2600 hsl(30, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.193 0.032 191.218)', // #011918 hsl(177, 95%, 5%)
-		primaryWhite: 'oklch(0.963 0.001 197.138)', // #F2F3F3 hsl(177, 5%, 95%)
-		darkBlack: 'oklch(0.18 0.034 82.944)', // #191001 hsl(38, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(38, 5%, 95%)
-		functional: {
-			success: 'oklch(0.618 0.208 142.486)', // #09a207 hsl(119, 92%, 33%)
-			warning: 'oklch(0.677 0.149 65.479)', // #d4810c hsl(35, 89%, 44%)
-			error: 'oklch(0.554 0.224 28.911)', // #d70909 hsl(0, 92%, 44%)
-			info: 'oklch(0.419 0.238 264.463)' // #062ecb hsl(228, 94%, 41%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.492 0.217 261.351)', // oklch(0.492 0.217 261.351) hsl(217, 100%, 43%)
-				alias: 'Tencent'
-			},
-			{
-				color: 'oklch(0.606 0.23 11.741)', // #ea1d5d hsl(341, 83%, 52%)
-				alias: 'Aparat'
-			},
-			{
-				color: 'oklch(0.588 0.134 239.667)', // #0085c3 hsl(199, 100%, 38%)
-				alias: 'Dell'
-			}
-		]
-	}
-};
-
-export default GreenJade;

+ 0 - 31
packages/stdf/src/data/themes/index.ts

@@ -1,31 +0,0 @@
-import { STDFTheme } from '$lib/theme/index.js';
-import Chameleon from './chameleon.js';
-import AzurePink from './azurePink.js';
-import SmartLive from './smartLive.js';
-import CamelliaRed from './camelliaRed.js';
-import Nintendo from './nintendo.js';
-import Skybloom from './skybloom.js';
-import Aqua from './aqua.js';
-import KleinH from './kleinH.js';
-import Azuper from './azuper.js';
-import Citrus from './citrus.js';
-import Lavender from './lavender.js';
-import TealMedley from './tealMedley.js';
-import GreenJade from './greenJade.js';
-
-export default [
-	{ name_CN: 'STDF', name: STDFTheme.name, theme: STDFTheme },
-	{ name_CN: '任天堂', name: Nintendo.name, theme: Nintendo },
-	{ name_CN: '蔚蓝粉', name: AzurePink.name, theme: AzurePink },
-	{ name_CN: '变色龙', name: Chameleon.name, theme: Chameleon },
-	{ name_CN: '慧活力', name: SmartLive.name, theme: SmartLive },
-	{ name_CN: '蓝采绿', name: Aqua.name, theme: Aqua },
-	{ name_CN: '山茶红', name: CamelliaRed.name, theme: CamelliaRed },
-	{ name_CN: '蓝粉梦', name: Skybloom.name, theme: Skybloom },
-	{ name_CN: '克莱仕', name: KleinH.name, theme: KleinH },
-	{ name_CN: '石青柿', name: Azuper.name, theme: Azuper },
-	{ name_CN: '青佛手', name: Citrus.name, theme: Citrus },
-	{ name_CN: '以初紫', name: Lavender.name, theme: Lavender },
-	{ name_CN: '青什锦', name: TealMedley.name, theme: TealMedley },
-	{ name_CN: '绿玉子', name: GreenJade.name, theme: GreenJade }
-];

+ 0 - 59
packages/stdf/src/data/themes/kleinH.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const KleinH: ThemeProps = {
-	name: 'KleinH',
-	color: {
-		primary: {
-			50: 'oklch(0.983 0.009 247.912)', // #F5FAFF hsl(210, 100%, 98%)
-			100: 'oklch(0.957 0.021 254.913)', // #E8F2FF hsl(214, 100%, 96%)
-			200: 'oklch(0.817 0.061 257.788)', // #ABC5EB hsl(216, 62%, 80%)
-			300: 'oklch(0.681 0.099 261.163)', // #7699D6 hsl(218, 54%, 65%)
-			400: 'oklch(0.559 0.134 262.606)', // #4871C2 hsl(220, 50%, 52%)
-			500: 'oklch(0.446 0.164 263.584)', // #214BAD hsl(222, 68%, 40%)
-			default: 'oklch(0.355 0.183 263.561)', // #022A99 hsl(224, 97%, 30%)
-			700: 'oklch(0.316 0.17 263.812)', // #012086 hsl(226, 99%, 27%)
-			800: 'oklch(0.28 0.155 264.078)', // #011873 hsl(228, 98%, 23%)
-			900: 'oklch(0.242 0.139 263.958)', // #001060 hsl(230, 100%, 19%)
-			950: 'oklch(0.207 0.12 264.016)' // #000A4D hsl(232, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.991 0.01 87.47)', // #FFFCF5 hsl(42, 100%, 98%)
-			100: 'oklch(0.978 0.022 83.264)', // #FFF7E8 hsl(39, 100%, 96%)
-			200: 'oklch(0.935 0.058 80.464)', // #FEE6BE hsl(38, 97%, 87%)
-			300: 'oklch(0.886 0.094 74.988)', // #FED193 hsl(35, 98%, 79%)
-			400: 'oklch(0.837 0.125 70.641)', // #FDBB69 hsl(33, 97%, 70%)
-			500: 'oklch(0.786 0.153 63.13)', // #FDA140 hsl(31, 98%, 62%)
-			default: 'oklch(0.734 0.178 54.177)', // #FC8416 hsl(29, 98%, 54%)
-			700: 'oklch(0.625 0.159 50.796)', // #D0650E hsl(27, 87%, 44%)
-			800: 'oklch(0.515 0.137 47.874)', // #A44907 hsl(25, 92%, 34%)
-			900: 'oklch(0.405 0.113 45.466)', // #783003 hsl(23, 95%, 24%)
-			950: 'oklch(0.295 0.084 45.171)' // #4D1B00 hsl(21, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.133 0.043 259.636)', // #010719 hsl(224, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(224, 5%, 95%)
-		darkBlack: 'oklch(0.17 0.034 67.389)', // #190C01 hsl(29, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(29, 5%, 95%)
-		functional: {
-			success: 'oklch(0.439 0.132 145.643)', // #07641d hsl(134, 87%, 21%)
-			warning: 'oklch(0.566 0.171 39.514)', // #c54711 hsl(18, 84%, 42%)
-			error: 'oklch(0.497 0.177 16.155)', // #b0213e hsl(348, 68%, 41%)
-			info: 'oklch(0.573 0.096 219.542)' // #1d859f hsl(192, 69%, 37%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.686 0.139 231.254)', // #00a8e0 hsl(195, 100%, 44%)
-				alias: 'AT&T'
-			},
-			{
-				color: 'oklch(0.651 0.212 142.608)', // oklch(0.651 0.212 142.608) hsl(120, 75%, 39%)
-				alias: 'WeChat'
-			},
-			{
-				color: 'oklch(0.652 0.199 0.712)', // #ea4c89 hsl(337, 79%, 61%)
-				alias: 'Dribbble'
-			}
-		]
-	}
-};
-
-export default KleinH;

+ 0 - 59
packages/stdf/src/data/themes/lavender.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Lavender: ThemeProps = {
-	name: 'Lavender',
-	color: {
-		primary: {
-			50: 'oklch(0.978 0.015 315.966)', // #FCF5FF hsl(282, 100%, 98%)
-			100: 'oklch(0.95 0.035 315.73)', // #F8E8FF hsl(282, 100%, 96%)
-			200: 'oklch(0.842 0.065 314.453)', // #DBBFE9 hsl(280, 49%, 83%)
-			300: 'oklch(0.739 0.091 312.314)', // #BE9AD4 hsl(277, 40%, 72%)
-			400: 'oklch(0.641 0.109 310.145)', // #A179BE hsl(275, 35%, 61%)
-			500: 'oklch(0.551 0.124 307.99)', // #865CA9 hsl(273, 31%, 51%)
-			default: 'oklch(0.467 0.13 305.627)', // #6C4393 hsl(271, 37%, 42%)
-			700: 'oklch(0.392 0.138 302.664)', // #552C81 hsl(269, 49%, 34%)
-			800: 'oklch(0.326 0.14 298.682)', // #401970 hsl(267, 64%, 27%)
-			900: 'oklch(0.269 0.132 293.856)', // #2D0B5E hsl(265, 79%, 21%)
-			950: 'oklch(0.216 0.123 289.042)' // #1D004D hsl(263, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.971 0.017 349.48)', // #FFF1F7 hsl(334, 100%, 97%)
-			100: 'oklch(0.953 0.029 347.076)', // #FFE8F3 hsl(331, 100%, 96%)
-			200: 'oklch(0.926 0.042 344.642)', // #FCDCED hsl(328, 84%, 93%)
-			300: 'oklch(0.901 0.053 344.002)', // #F9D1E7 hsl(327, 77%, 90%)
-			400: 'oklch(0.874 0.065 342.674)', // #F5C5E1 hsl(325, 71%, 87%)
-			500: 'oklch(0.85 0.078 340.995)', // #F2BADD hsl(323, 68%, 84%)
-			default: 'oklch(0.826 0.092 339.861)', // #EFAFD9 hsl(321, 67%, 81%)
-			700: 'oklch(0.654 0.135 340.177)', // #C66DAA hsl(319, 44%, 60%)
-			800: 'oklch(0.512 0.156 340.793)', // #9E3A81 hsl(317, 46%, 42%)
-			900: 'oklch(0.391 0.15 340.551)', // #75155D hsl(315, 70%, 27%)
-			950: 'oklch(0.284 0.123 340.171)' // #4D003C hsl(313, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.129 0.059 306.37)', // #0D0119 hsl(271, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(271, 5%, 95%)
-		darkBlack: 'oklch(0.146 0.057 343.966)', // #190110 hsl(321, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(321, 5%, 95%)
-		functional: {
-			success: 'oklch(0.784 0.194 147.722)', // #4ad96b hsl(134, 65%, 57%)
-			warning: 'oklch(0.693 0.147 42.888)', // #e67a4c hsl(18, 76%, 60%)
-			error: 'oklch(0.639 0.192 24.341)', // #e94e4e hsl(0, 78%, 61%)
-			info: 'oklch(0.613 0.168 270.082)' // #5e7ae8 hsl(228, 75%, 64%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.492 0.217 261.351)', // oklch(0.492 0.217 261.351) hsl(217, 100%, 43%)
-				alias: 'Tencent'
-			},
-			{
-				color: 'oklch(0.651 0.212 142.608)', // oklch(0.651 0.212 142.608) hsl(120, 75%, 39%)
-				alias: 'WeChat'
-			},
-			{
-				color: 'oklch(0.343 0.077 256.446)', // #1c3960 hsl(214, 55%, 24%)
-				alias: 'Ford'
-			}
-		]
-	}
-};
-
-export default Lavender;

+ 0 - 59
packages/stdf/src/data/themes/nintendo.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Nintendo: ThemeProps = {
-	name: 'Nintendo',
-	color: {
-		primary: {
-			50: 'oklch(0.98 0.01 25.073)', // #FFF6F5 hsl(6, 100%, 98%)
-			100: 'oklch(0.953 0.023 24.126)', // #FFEAE8 hsl(5, 100%, 96%)
-			200: 'oklch(0.845 0.076 21.796)', // #FAB9B6 hsl(3, 87%, 85%)
-			300: 'oklch(0.745 0.133 21.872)', // #F58886 hsl(1, 85%, 74%)
-			400: 'oklch(0.662 0.188 22.749)', // #F0575A hsl(359, 84%, 64%)
-			500: 'oklch(0.609 0.224 25.256)', // #EB2B34 hsl(357, 83%, 55%)
-			default: 'oklch(0.581 0.238 27.862)', // #E60012 hsl(355, 100%, 45%)
-			700: 'oklch(0.508 0.207 26.549)', // #C00016 hsl(353, 100%, 38%)
-			800: 'oklch(0.43 0.174 24.608)', // #990017 hsl(351, 100%, 30%)
-			900: 'oklch(0.351 0.141 21.971)', // #730015 hsl(349, 100%, 23%)
-			950: 'oklch(0.266 0.107 17.45)' // #4D0011 hsl(347, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.992 0.011 189.833)', // #F5FFFE hsl(174, 100%, 98%)
-			100: 'oklch(0.983 0.024 193.6)', // #E8FFFE hsl(177, 100%, 96%)
-			200: 'oklch(0.938 0.067 196.074)', // #B6F9F9 hsl(180, 85%, 85%)
-			300: 'oklch(0.893 0.099 198.69)', // #85F0F4 hsl(182, 84%, 74%)
-			400: 'oklch(0.847 0.12 201.806)', // #57E4EE hsl(184, 82%, 64%)
-			500: 'oklch(0.803 0.131 206.757)', // #2AD6E9 hsl(186, 81%, 54%)
-			default: 'oklch(0.752 0.133 215.123)', // #00C3E3 hsl(189, 100%, 45%)
-			700: 'oklch(0.646 0.116 218.359)', // #009EBD hsl(190, 100%, 37%)
-			800: 'oklch(0.535 0.1 223.819)', // #007998 hsl(192, 100%, 30%)
-			900: 'oklch(0.426 0.083 227.589)', // #005772 hsl(194, 100%, 22%)
-			950: 'oklch(0.319 0.064 230.58)' // #00384D hsl(196, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.139 0.05 18.003)', // #190103 hsl(355, 95%, 5%)
-		primaryWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(355, 5%, 95%)
-		darkBlack: 'oklch(0.184 0.03 206.812)', // #011619 hsl(188, 95%, 5%)
-		darkWhite: 'oklch(0.963 0.001 197.138)', // #F2F3F3 hsl(188, 5%, 95%)
-		functional: {
-			success: 'oklch(0.669 0.214 144.385)', // #00b42a hsl(134, 100%, 35%)
-			warning: 'oklch(0.865 0.176 95.103)', // #f7d00c hsl(50, 94%, 51%)
-			error: 'oklch(0.642 0.218 25.88)', // #f53f3f hsl(0, 90%, 60%)
-			info: 'oklch(0.549 0.249 263.048)' // #165dff hsl(222, 100%, 54%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.492 0.217 261.351)', // #0052d9 hsl(217, 100%, 43%)
-				alias: 'Tencent'
-			},
-			{
-				color: 'oklch(0.651 0.212 142.608)', // #1aad19 hsl(120, 75%, 39%)
-				alias: 'WeChat'
-			},
-			{
-				color: 'oklch(0.692 0.181 46.213)', // #f37021 hsl(23, 90%, 54%)
-				alias: 'Hermès'
-			}
-		]
-	}
-};
-
-export default Nintendo;

+ 0 - 59
packages/stdf/src/data/themes/skybloom.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const Skybloom: ThemeProps = {
-	name: 'Skybloom',
-	color: {
-		primary: {
-			50: 'oklch(0.987 0.008 225.079)', // #F5FCFF hsl(198, 100%, 98%)
-			100: 'oklch(0.969 0.019 225.712)', // #E8F8FF hsl(198, 100%, 96%)
-			200: 'oklch(0.863 0.05 229.685)', // #B1D9ED hsl(200, 63%, 81%)
-			300: 'oklch(0.759 0.075 234.966)', // #81B9DA hsl(202, 55%, 68%)
-			400: 'oklch(0.66 0.098 240.067)', // #569AC8 hsl(204, 51%, 56%)
-			500: 'oklch(0.566 0.115 244.99)', // #317CB5 hsl(206, 57%, 45%)
-			default: 'oklch(0.479 0.13 251.007)', // #125FA3 hsl(208, 80%, 36%)
-			700: 'oklch(0.42 0.122 253.316)', // #0C4D8D hsl(210, 84%, 30%)
-			800: 'oklch(0.359 0.116 256.313)', // #073B78 hsl(212, 89%, 25%)
-			900: 'oklch(0.303 0.105 257.617)', // #032C62 hsl(214, 94%, 20%)
-			950: 'oklch(0.251 0.093 258.021)' // #001F4D hsl(216, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.975 0.012 23.703)', // #FFF4F3 hsl(5, 100%, 98%)
-			100: 'oklch(0.949 0.025 17.604)', // #FFE8E8 hsl(0, 100%, 96%)
-			200: 'oklch(0.92 0.038 17.861)', // #FDDBDB hsl(0, 90%, 93%)
-			300: 'oklch(0.89 0.05 15.051)', // #FACED0 hsl(357, 82%, 89%)
-			400: 'oklch(0.861 0.063 12.376)', // #F8C1C6 hsl(355, 80%, 87%)
-			500: 'oklch(0.832 0.076 10.667)', // #F5B4BC hsl(353, 77%, 83%)
-			default: 'oklch(0.807 0.089 9.599)', // #F3A8B3 hsl(351, 76%, 81%)
-			700: 'oklch(0.635 0.124 9.491)', // #C9687A hsl(349, 47%, 60%)
-			800: 'oklch(0.493 0.139 10.638)', // #A0374E hsl(347, 49%, 42%)
-			900: 'oklch(0.374 0.131 12.448)', // #76142D hsl(345, 71%, 27%)
-			950: 'oklch(0.268 0.107 13.338)' // #4D0016 hsl(343, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.156 0.033 240.386)', // #010E19 hsl(208, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(208, 5%, 95%)
-		darkBlack: 'oklch(0.14 0.05 14.109)', // #190104 hsl(351, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(351, 5%, 95%)
-		functional: {
-			success: 'oklch(0.7 0.181 152.201)', // #0fbd63 hsl(149, 85%, 40%)
-			warning: 'oklch(0.534 0.132 51.034)', // #a75211 hsl(26, 82%, 36%)
-			error: 'oklch(0.58 0.211 10.496)', // #d9265c hsl(342, 70%, 50%)
-			info: 'oklch(0.458 0.218 283.692)' // #5231c9 hsl(253, 61%, 49%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.703 0.149 235.113)', // oklch(0.703 0.149 235.059) hsl(197, 98%, 47%)
-				alias: 'Twitter'
-			},
-			{
-				color: 'oklch(0.702 0.194 38.137)', // oklch(0.702 0.194 38.137) hsl(15, 100%, 61%)
-				alias: 'Svelte'
-			},
-			{
-				color: 'oklch(0.518 0.199 259.881)', // #0e5fd8 hsl(216, 88%, 45%)
-				alias: 'Philips'
-			}
-		]
-	}
-};
-
-export default Skybloom;

+ 0 - 59
packages/stdf/src/data/themes/smartLive.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const SmartLive: ThemeProps = {
-	name: 'SmartLive',
-	color: {
-		primary: {
-			50: 'oklch(0.978 0.015 315.966)', // #FCF5FF hsl(282, 100%, 98%)
-			100: 'oklch(0.95 0.035 315.73)', // #F8E8FF hsl(282, 100%, 96%)
-			200: 'oklch(0.794 0.075 313.533)', // #CDAEDE hsl(279, 42%, 78%)
-			300: 'oklch(0.652 0.103 312.25)', // #A57DBD hsl(278, 33%, 62%)
-			400: 'oklch(0.522 0.119 309.358)', // #7E549D hsl(275, 30%, 47%)
-			500: 'oklch(0.406 0.122 307.073)', // #5B337C hsl(273, 42%, 34%)
-			default: 'oklch(0.304 0.112 304.532)', // #3C1A5B hsl(271, 56%, 23%)
-			700: 'oklch(0.28 0.115 301.336)', // #341357 hsl(269, 64%, 21%)
-			800: 'oklch(0.258 0.119 297.251)', // #2C0C54 hsl(267, 75%, 19%)
-			900: 'oklch(0.238 0.12 293.937)', // #250650 hsl(265, 86%, 17%)
-			950: 'oklch(0.216 0.123 289.042)' // #1D004D hsl(263, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.98 0.01 25.073)', // #FFF6F5 hsl(6, 100%, 98%)
-			100: 'oklch(0.953 0.023 24.126)', // #FFEAE8 hsl(5, 100%, 96%)
-			200: 'oklch(0.902 0.051 19.634)', // #FFD2D1 hsl(1, 100%, 91%)
-			300: 'oklch(0.853 0.08 18.86)', // #FFBABA hsl(0, 100%, 87%)
-			400: 'oklch(0.806 0.111 17.023)', // #FFA2A6 hsl(357, 100%, 82%)
-			500: 'oklch(0.765 0.14 16.578)', // #FF8B93 hsl(356, 100%, 77%)
-			default: 'oklch(0.728 0.169 15.672)', // #FF7483 hsl(354, 100%, 73%)
-			700: 'oklch(0.597 0.173 16.54)', // #D2485A hsl(352, 61%, 55%)
-			800: 'oklch(0.482 0.163 17.177)', // #A6263B hsl(350, 63%, 40%)
-			900: 'oklch(0.373 0.138 17.973)', // #790E23 hsl(348, 79%, 27%)
-			950: 'oklch(0.267 0.107 16.66)' // #4D0012 hsl(346, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.129 0.059 306.37)', // #0D0119 hsl(271, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(271, 5%, 95%)
-		darkBlack: 'oklch(0.139 0.05 18.003)', // #190103 hsl(354, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(354, 5%, 95%)
-		functional: {
-			success: 'oklch(0.419 0.092 155.284)', // #155b36 hsl(148, 63%, 22%)
-			warning: 'oklch(0.666 0.147 65.222)', // #d07e0b hsl(35, 90%, 43%)
-			error: 'oklch(0.564 0.223 28.46)', // #da1414 hsl(0, 83%, 47%)
-			info: 'oklch(0.728 0.107 239.498)' // #63b0e3 hsl(204, 70%, 64%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.713 0.194 130.802)', // #76b900 hsl(82, 100%, 36%)
-				alias: 'NVIDIA'
-			},
-			{
-				color: 'oklch(0.726 0.171 4.454)', // #fb7299 hsl(343, 95%, 72%)
-				alias: 'bilibili'
-			},
-			{
-				color: 'oklch(0.599 0.219 259.04)', // #1677ff hsl(215, 100%, 54%)
-				alias: 'AliPay'
-			}
-		]
-	}
-};
-
-export default SmartLive;

+ 0 - 59
packages/stdf/src/data/themes/tealMedley.ts

@@ -1,59 +0,0 @@
-import type { ThemeProps } from '$lib/theme/types.js';
-
-const TealMedley: ThemeProps = {
-	name: 'TealMedley',
-	color: {
-		primary: {
-			50: 'oklch(0.991 0.01 204.917)', // #F5FEFF hsl(186, 100%, 98%)
-			100: 'oklch(0.977 0.021 207.588)', // #E8FCFF hsl(188, 100%, 96%)
-			200: 'oklch(0.877 0.046 213.81)', // #B5DFE9 hsl(192, 54%, 81%)
-			300: 'oklch(0.78 0.063 217.138)', // #89C2D2 hsl(193, 45%, 68%)
-			400: 'oklch(0.686 0.076 222.122)', // #62A5BC hsl(195, 40%, 56%)
-			500: 'oklch(0.595 0.084 226.019)', // #4089A5 hsl(197, 44%, 45%)
-			default: 'oklch(0.509 0.088 231.916)', // #256E8F hsl(199, 59%, 35%)
-			700: 'oklch(0.448 0.086 236.944)', // #195B7E hsl(201, 67%, 30%)
-			800: 'oklch(0.389 0.085 242.356)', // #0E496E hsl(203, 77%, 24%)
-			900: 'oklch(0.334 0.081 245.985)', // #06395D hsl(205, 88%, 19%)
-			950: 'oklch(0.28 0.078 249.311)' // #002A4D hsl(207, 100%, 15%)
-		},
-		dark: {
-			50: 'oklch(0.971 0.015 33.071)', // #FFF2EF hsl(11, 100%, 97%)
-			100: 'oklch(0.957 0.021 31.78)', // #FFECE8 hsl(10, 100%, 96%)
-			200: 'oklch(0.932 0.028 28.52)', // #FBE2DE hsl(8, 78%, 93%)
-			300: 'oklch(0.905 0.036 24.203)', // #F7D7D4 hsl(5, 69%, 90%)
-			400: 'oklch(0.879 0.042 23.456)', // #F2CDCA hsl(5, 61%, 87%)
-			500: 'oklch(0.852 0.051 21.242)', // #EEC2C0 hsl(3, 58%, 84%)
-			default: 'oklch(0.825 0.059 18.425)', // #EAB7B7 hsl(0, 55%, 82%)
-			700: 'oklch(0.642 0.102 17.954)', // #C37275 hsl(358, 40%, 61%)
-			800: 'oklch(0.49 0.126 19.197)', // #9B3D43 hsl(356, 44%, 42%)
-			900: 'oklch(0.369 0.126 21.232)', // #741720 hsl(354, 67%, 27%)
-			950: 'oklch(0.265 0.107 22.403)' // #4D000A hsl(352, 100%, 15%)
-		},
-		primaryBlack: 'oklch(0.167 0.03 228.62)', // #011119 hsl(199, 95%, 5%)
-		primaryWhite: 'oklch(0.961 0.001 286.375)', // #F2F2F3 hsl(199, 5%, 95%)
-		darkBlack: 'oklch(0.138 0.051 27.08)', // #190101 hsl(0, 95%, 5%)
-		darkWhite: 'oklch(0.962 0.001 17.178)', // #F3F2F2 hsl(0, 5%, 95%)
-		functional: {
-			success: 'oklch(0.608 0.142 148.314)', // #38994f hsl(134, 46%, 41%)
-			warning: 'oklch(0.776 0.119 100.626)', // #c7b857 hsl(52, 50%, 56%)
-			error: 'oklch(0.66 0.146 21.85)', // #dd6969 hsl(0, 63%, 64%)
-			info: 'oklch(0.559 0.131 271.419)' // #596dc0 hsl(228, 45%, 55%)
-		},
-		extend: [
-			{
-				color: 'oklch(0.492 0.217 261.351)', // oklch(0.492 0.217 261.351) hsl(217, 100%, 43%)
-				alias: 'Tencent'
-			},
-			{
-				color: 'oklch(0.424 0.097 251.575)', // #205081 hsl(210, 60%, 32%)
-				alias: 'Bitbucket'
-			},
-			{
-				color: 'oklch(0.364 0.191 266.82)', // #1428a0 hsl(231, 78%, 35%)
-				alias: 'Samsung'
-			}
-		]
-	}
-};
-
-export default TealMedley;

+ 150 - 0
packages/stdf/src/lib/components/accordion/Accordion.svelte

@@ -0,0 +1,150 @@
+<script lang="ts">
+	import { slide } from 'svelte/transition';
+	import type { AccordionProps } from '../../types/index.js';
+	import Icon from '../icon/Icon.svelte';
+
+	let {
+		items = [],
+		activeIndex = $bindable<number | number[] | undefined>(undefined),
+		multiple = false,
+		radius = 'md',
+		border = 'solid',
+		divider = true,
+		expandIcon = 'arrow',
+		iconPosition = 'right',
+		transitionDuration = 300,
+		injClass = '',
+		titleClass = '',
+		contentClass = '',
+		children,
+		onchange
+	}: AccordionProps = $props();
+
+	// 圆角样式映射
+	// Radius style mapping
+	const radiusObj: Record<string, string> = {
+		none: 'rounded-none',
+		sm: 'rounded-sm',
+		md: 'rounded-md',
+		lg: 'rounded-lg',
+		xl: 'rounded-xl',
+		'2xl': 'rounded-2xl',
+		'3xl': 'rounded-3xl',
+		'4xl': 'rounded-4xl',
+		'': 'rounded-(--radius-box)'
+	};
+
+	// 边框样式映射
+	// Border style mapping
+	const borderObj: Record<string, string> = {
+		none: '',
+		solid: 'border border-solid border-black/10 dark:border-white/10',
+		dashed: 'border border-dashed border-black/10 dark:border-white/10',
+		dotted: 'border border-dotted border-black/10 dark:border-white/10'
+	};
+
+	// 分割线样式映射(与边框样式保持一致)
+	// Divider style mapping (consistent with border style)
+	const dividerObj: Record<string, string> = {
+		none: 'border-t border-black/10 dark:border-white/10',
+		solid: 'border-t border-solid border-black/10 dark:border-white/10',
+		dashed: 'border-t border-dashed border-black/10 dark:border-white/10',
+		dotted: 'border-t border-dotted border-black/10 dark:border-white/10'
+	};
+
+	// 判断某项是否展开
+	// Check if an item is expanded
+	const isExpanded = (index: number): boolean => {
+		if (activeIndex === undefined) return false;
+		if (multiple && Array.isArray(activeIndex)) {
+			return activeIndex.includes(index);
+		}
+		return activeIndex === index;
+	};
+
+	// 切换展开/折叠
+	// Toggle expand/collapse
+	const toggle = (index: number) => {
+		const item = items[index];
+		if (item?.disabled) return;
+
+		if (multiple) {
+			// 多选模式
+			// Multiple selection mode
+			const currentActive = Array.isArray(activeIndex) ? [...activeIndex] : [];
+			const idx = currentActive.indexOf(index);
+			if (idx > -1) {
+				currentActive.splice(idx, 1);
+			} else {
+				currentActive.push(index);
+			}
+			activeIndex = currentActive;
+			onchange?.(currentActive);
+		} else {
+			// 单选模式
+			// Single selection mode
+			activeIndex = activeIndex === index ? undefined : index;
+			onchange?.(activeIndex as number);
+		}
+	};
+
+	// 展开图标的旋转样式
+	// Expand icon rotation style
+	const getIconRotation = (index: number): string => {
+		if (!isExpanded(index)) return '';
+		if (expandIcon === 'arrow') return 'rotate-90';
+		if (expandIcon === 'plus') return 'rotate-45';
+		return '';
+	};
+</script>
+
+<div class="overflow-hidden {radiusObj[radius]} {borderObj[border]} {injClass}">
+	{#each items as item, index (index)}
+		{@const expanded = isExpanded(index)}
+		<div class={index > 0 && divider ? dividerObj[border] : ''}>
+			<!-- 标题区域 Title area -->
+			<button
+				type="button"
+				class="flex w-full items-center justify-between px-4 py-3 text-left transition-colors
+					{item.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer active:bg-black/5 dark:active:bg-white/5'}
+					{iconPosition === 'left' ? 'flex-row-reverse' : ''}
+					{titleClass}"
+				onclick={() => toggle(index)}
+				disabled={item.disabled}
+			>
+				<div class="flex flex-1 items-center gap-2 {iconPosition === 'left' ? 'flex-row-reverse justify-end' : ''}">
+					{#if item.icon}
+						<Icon {...item.icon} />
+					{/if}
+					<span class="font-medium">{item.title}</span>
+				</div>
+				{#if expandIcon}
+					<span class="shrink-0 transition-transform duration-300 {getIconRotation(index)}">
+						{#if expandIcon === 'arrow'}
+							<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
+								<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
+							</svg>
+						{:else if expandIcon === 'plus'}
+							<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
+								<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
+							</svg>
+						{/if}
+					</span>
+				{/if}
+			</button>
+
+			<!-- 内容区域 Content area -->
+			{#if expanded}
+				<div class="overflow-hidden" transition:slide={{ duration: transitionDuration }}>
+					<div class="px-4 pb-4 text-sm text-black/70 dark:text-white/70 {contentClass}">
+						{#if children}
+							{@render children(item, index)}
+						{:else if item.content}
+							{item.content}
+						{/if}
+					</div>
+				</div>
+			{/if}
+		</div>
+	{/each}
+</div>

+ 484 - 0
packages/stdf/src/lib/components/actionPopover/ActionPopover.svelte

@@ -0,0 +1,484 @@
+<script lang="ts">
+	import { getContext, onMount, onDestroy, tick } from 'svelte';
+	import { scale, fly } from 'svelte/transition';
+	import { cubicOut } from 'svelte/easing';
+	import Icon from '../icon/Icon.svelte';
+	import type { ActionPopoverProps, ActionProps, RingActionProps } from '../../types/index.js';
+	import { zh_CN, type LangProps } from '../../lang/index.js';
+
+	// 当前语言
+	// current language
+	const currentLang = (getContext('STDF_lang') || zh_CN) as LangProps;
+	const actionPopoverLang: LangProps['actionSheet'] = currentLang.actionSheet;
+
+	let {
+		visible = $bindable(false),
+		title = '',
+		titleAlign = 'center',
+		actions = [],
+		showCancel = false,
+		cancelText = actionPopoverLang.cancelText,
+		actionClosable = true,
+		align = 'center',
+		inverse = false,
+		layout = 'v',
+		gridColumns = 3,
+		// inline 模式相关属性
+		// inline mode related props
+		triggerRef = null,
+		inlineAlign = 'center',
+		inlineDirection = 'auto',
+		inlineOffset = 8,
+		inlineShadow = 'md',
+		inlineRadius = '',
+		// 环形布局相关属性
+		// ring layout related props
+		ringActions = [],
+		ringRadius = 0,
+		ringItemSize = 44,
+		ringShape = 'auto',
+		oncancel,
+		onclickAction,
+		onclose
+	}: ActionPopoverProps = $props();
+
+	// inline 模式面板引用
+	// inline mode panel reference
+	let panelRef = $state<HTMLElement | null>(null);
+
+	// 位置是否已计算完成
+	// whether position has been calculated
+	let positionReady = $state(false);
+
+	// inline 模式位置状态
+	// inline mode position state
+	let inlinePosition = $state({ top: 0, left: 0 });
+	let actualDirection = $state<'up' | 'down'>('down');
+
+	// 静态样式类定义移到组件外部
+	// static style class definition moved to component outside
+	const STYLE_MAPS = {
+		titleAlign: { left: 'text-left', center: 'text-center', right: 'text-right' },
+		state: {
+			normal: ' ',
+			theme: ' text-primary dark:text-dark ',
+			success: ' text-success ',
+			warning: ' text-warning ',
+			error: ' text-error ',
+			info: ' text-info '
+		},
+		imgRadius: {
+			none: 'rounded-none',
+			xs: 'rounded-xs',
+			sm: 'rounded-sm',
+			md: 'rounded-md',
+			lg: 'rounded-lg',
+			xl: 'rounded-xl',
+			'2xl': 'rounded-2xl',
+			full: 'rounded-full'
+		},
+		align: { left: 'justify-start pl-4', center: 'justify-center', right: 'justify-end pr-4' },
+		shadow: {
+			none: 'shadow-none',
+			xs: 'shadow-xs dark:shadow-white/5',
+			sm: 'shadow-sm dark:shadow-white/5',
+			md: 'shadow-md dark:shadow-white/10',
+			lg: 'shadow-lg dark:shadow-white/10',
+			xl: 'shadow-xl dark:shadow-white/10',
+			'2xl': 'shadow-2xl dark:shadow-white/20'
+		},
+		radius: {
+			none: 'rounded-none',
+			sm: 'rounded-sm',
+			md: 'rounded-md',
+			lg: 'rounded-lg',
+			xl: 'rounded-xl',
+			'2xl': 'rounded-2xl',
+			'3xl': 'rounded-3xl',
+			'4xl': 'rounded-4xl'
+		},
+		gridColumns: {
+			2: 'grid-cols-2',
+			3: 'grid-cols-3',
+			4: 'grid-cols-4'
+		}
+	};
+
+	// 反转色样式。
+	// Tailwind safelist: bg-bg-surface-dark dark:bg-bg-surface border-white/10 dark:border-black/5 text-text-dark dark:text-text-primary text-text-dark/60 dark:text-text-primary/60 bg-white/10 dark:bg-black/10 divide-white/10 dark:divide-black/10 active:bg-text-dark/10 dark:active:bg-text-primary/10
+	const inversePanelClass = $derived(
+		inverse
+			? 'bg-bg-surface-dark dark:bg-bg-surface border-white/10 dark:border-black/5 text-text-dark dark:text-text-primary'
+			: 'bg-bg-surface dark:bg-bg-surface-dark border-black/5 dark:border-white/10'
+	);
+	const inverseTitleClass = $derived(
+		inverse ? 'text-text-dark/60 dark:text-text-primary/60 border-white/10 dark:border-black/5' : 'text-black/50 dark:text-white/50 border-black/5 dark:border-white/5'
+	);
+	const inverseDescClass = $derived(inverse ? 'text-text-dark/60 dark:text-text-primary/60' : 'text-black/50 dark:text-white/40');
+	const inverseDividerClass = $derived(inverse ? 'bg-white/10 dark:bg-black/10' : 'bg-black/5 dark:bg-white/5');
+	const inverseDivideClass = $derived(inverse ? 'divide-white/10 dark:divide-black/10' : 'divide-black/5 dark:divide-white/5');
+	const inverseActiveClass = $derived(inverse ? 'active:bg-text-dark/10 dark:active:bg-text-primary/10' : 'active:bg-black/5 dark:active:bg-white/5');
+	const inverseItemBgClass = $derived(inverse ? 'bg-bg-surface-dark dark:bg-bg-surface' : 'bg-bg-surface dark:bg-bg-surface-dark');
+
+	const actionContainerClass = $derived(
+		layout === 'grid'
+			? `grid ${STYLE_MAPS.gridColumns[gridColumns] || STYLE_MAPS.gridColumns[3]} gap-px ${inverseDividerClass}`
+			: layout === 'h'
+				? `flex items-stretch divide-x ${inverseDivideClass}`
+				: ''
+	);
+
+	const actionLayoutClass = $derived(layout === 'grid' ? `w-full ${inverseItemBgClass}` : layout === 'h' ? 'flex-1' : '');
+
+	// 环形布局相关计算
+	// ring layout related calculations
+	let ringPosition = $state({ x: 0, y: 0 });
+	let computedRingShape = $state<'full' | 'half' | 'quarter'>('quarter');
+	let ringStartAngle = $state(0);
+
+	// 根据触发元素位置和操作项数量自动计算环形形状和起始角度
+	// auto calculate ring shape and start angle based on trigger position and action count
+	const calculateRingLayout = () => {
+		if (!triggerRef || layout !== 'ring') return;
+
+		const triggerRect = triggerRef.getBoundingClientRect();
+		const viewportWidth = window.innerWidth;
+		const viewportHeight = window.innerHeight;
+
+		// 触发元素中心点
+		// trigger element center point
+		const triggerCenterX = triggerRect.left + triggerRect.width / 2;
+		const triggerCenterY = triggerRect.top + triggerRect.height / 2;
+
+		// 判断触发元素在视口的位置(左/右、上/下)
+		// determine trigger position in viewport (left/right, top/bottom)
+		const isLeft = triggerCenterX < viewportWidth / 2;
+		const isTop = triggerCenterY < viewportHeight / 2;
+
+		// 设置环形中心位置(相对于触发元素)
+		// set ring center position (relative to trigger element)
+		ringPosition = {
+			x: triggerCenterX,
+			y: triggerCenterY
+		};
+
+		const itemCount = ringActions.length;
+
+		// 根据 ringShape 属性或自动判断形状
+		// determine shape based on ringShape prop or auto
+		if (ringShape !== 'auto') {
+			computedRingShape = ringShape;
+		} else {
+			// 自动判断:根据操作项数量
+			// auto: based on action count
+			if (itemCount <= 3) {
+				computedRingShape = 'quarter';
+			} else if (itemCount <= 5) {
+				computedRingShape = 'half';
+			} else {
+				computedRingShape = 'full';
+			}
+		}
+
+		// 根据位置计算起始角度
+		// calculate start angle based on position
+		if (computedRingShape === 'quarter') {
+			// 1/4 圆:根据四个角落确定起始角度
+			// quarter: determine start angle based on corner
+			if (isTop && isLeft) {
+				ringStartAngle = 0; // 右下方向展开
+			} else if (isTop && !isLeft) {
+				ringStartAngle = 90; // 左下方向展开
+			} else if (!isTop && !isLeft) {
+				ringStartAngle = 180; // 左上方向展开
+			} else {
+				ringStartAngle = 270; // 右上方向展开
+			}
+		} else if (computedRingShape === 'half') {
+			// 半圆:根据上下位置确定起始角度
+			// half: determine start angle based on top/bottom
+			if (isTop) {
+				ringStartAngle = 0; // 向下展开
+			} else {
+				ringStartAngle = 180; // 向上展开
+			}
+		} else {
+			// 全圆:从右侧开始
+			// full: start from right
+			ringStartAngle = 0;
+		}
+	};
+
+	// 计算每个操作项的位置
+	// calculate position for each action item
+	const getRingItemPosition = (index: number) => {
+		const itemCount = ringActions.length;
+		const radius = ringRadius || Math.max(60, ringItemSize * 1.5);
+
+		let angleSpan: number;
+		let startAngle: number;
+
+		if (computedRingShape === 'quarter') {
+			angleSpan = 90;
+			startAngle = ringStartAngle;
+		} else if (computedRingShape === 'half') {
+			angleSpan = 180;
+			startAngle = ringStartAngle;
+		} else {
+			angleSpan = 360;
+			startAngle = ringStartAngle;
+		}
+
+		// 计算每个项的角度
+		// calculate angle for each item
+		const angleStep = itemCount > 1 ? angleSpan / (itemCount - 1 + (computedRingShape === 'full' ? 1 : 0)) : 0;
+		const angle = startAngle + index * angleStep;
+		const radian = (angle * Math.PI) / 180;
+
+		return {
+			x: Math.cos(radian) * radius,
+			y: Math.sin(radian) * radius
+		};
+	};
+
+	// 计算 inline 模式位置
+	// calculate inline mode position
+	const calculateInlinePosition = () => {
+		if (!triggerRef || !panelRef) return;
+
+		const triggerRect = triggerRef.getBoundingClientRect();
+		const panelRect = panelRef.getBoundingClientRect();
+		const panelWidth = panelRef.offsetWidth || panelRect.width;
+		const panelHeight = panelRef.offsetHeight || panelRect.height;
+		const viewportHeight = window.innerHeight;
+		const viewportWidth = window.innerWidth;
+
+		// 计算垂直方向
+		// calculate vertical direction
+		if (inlineDirection === 'auto') {
+			const triggerCenter = triggerRect.top + triggerRect.height / 2;
+			const viewportCenter = viewportHeight / 2;
+			actualDirection = triggerCenter > viewportCenter ? 'up' : 'down';
+		} else {
+			actualDirection = inlineDirection as 'up' | 'down';
+		}
+
+		// 计算 top 位置
+		// calculate top position
+		let top: number;
+		if (actualDirection === 'down') {
+			top = triggerRect.bottom + inlineOffset;
+			// 边界检测:如果超出底部,则向上弹出
+			// boundary detection: if exceeds bottom, pop up
+			if (top + panelHeight > viewportHeight - 8) {
+				actualDirection = 'up';
+				top = triggerRect.top - panelHeight;
+			}
+		} else {
+			top = triggerRect.top - panelHeight;
+			// 边界检测:如果超出顶部,则向下弹出
+			// boundary detection: if exceeds top, pop down
+			if (top < 8) {
+				actualDirection = 'down';
+				top = triggerRect.bottom + inlineOffset;
+			}
+		}
+
+		// 计算水平位置
+		// calculate horizontal position
+		let left: number;
+		if (inlineAlign === 'left') {
+			left = triggerRect.left;
+		} else if (inlineAlign === 'right') {
+			left = triggerRect.right - panelWidth;
+		} else {
+			// center
+			left = triggerRect.left + (triggerRect.width - panelWidth) / 2;
+		}
+
+		// 水平边界约束
+		// horizontal boundary constraint
+		left = Math.max(8, Math.min(left, viewportWidth - panelWidth - 8));
+
+		inlinePosition = { top, left };
+	};
+
+	// 计算 transform-origin
+	// calculate transform-origin
+	const transformOrigin = $derived(() => {
+		const vertical = actualDirection === 'down' ? 'top' : 'bottom';
+		return `${vertical} ${inlineAlign}`;
+	});
+
+	// 点击外部关闭
+	// click outside to close
+	const handleClickOutside = (event: MouseEvent) => {
+		if (!visible) return;
+		const target = event.target as Node;
+		if (panelRef && !panelRef.contains(target) && triggerRef && !triggerRef.contains(target)) {
+			visible = false;
+			onclose?.();
+		}
+	};
+
+	// 优化事件处理函数
+	// optimize event handling function
+	const handleCancel = () => {
+		visible = false;
+		oncancel?.();
+		onclose?.();
+	};
+
+	// 处理选项点击事件
+	// handle option click event
+	const handleActionClick = (index: number, item: ActionProps) => {
+		if (item.disabled) return;
+		onclickAction?.(index, item);
+		if (actionClosable) {
+			visible = false;
+			onclose?.();
+		}
+	};
+
+	// 处理环形操作项点击事件
+	// handle ring action click event
+	const handleRingActionClick = (index: number, item: RingActionProps) => {
+		if (item.disabled) return;
+		onclickAction?.(index, item);
+		if (actionClosable) {
+			visible = false;
+			onclose?.();
+		}
+	};
+
+	// 监听 visible 变化,更新位置
+	// watch visible change, update position
+	$effect(() => {
+		if (visible && triggerRef) {
+			positionReady = false;
+			// 使用 tick 确保 DOM 已完全更新
+			// use tick to ensure DOM has been fully updated
+			tick().then(() => {
+				requestAnimationFrame(() => {
+					if (layout === 'ring') {
+						calculateRingLayout();
+					} else {
+						calculateInlinePosition();
+					}
+					positionReady = true;
+				});
+			});
+		} else if (!visible) {
+			positionReady = false;
+		}
+	});
+
+	onMount(() => {
+		document.addEventListener('click', handleClickOutside);
+	});
+
+	onDestroy(() => {
+		document.removeEventListener('click', handleClickOutside);
+	});
+</script>
+
+{#if visible}
+	{#if layout === 'ring'}
+		<!-- 环形布局 Ring layout -->
+		{#if positionReady}
+			<div
+				bind:this={panelRef}
+				class="fixed z-50"
+				style="top: {ringPosition.y}px; left: {ringPosition.x}px; transform: translate(-50%, -50%);"
+			>
+				{#each ringActions as item, index (index)}
+					{@const pos = getRingItemPosition(index)}
+					{@const isDisabled = item.disabled}
+					{@const itemState = item.style || 'normal'}
+					{@const iconState = item.icon?.state || (itemState !== 'normal' ? itemState : undefined)}
+					<button
+						class="absolute flex items-center justify-center rounded-full border transition-all {inversePanelClass} {STYLE_MAPS.shadow[inlineShadow] || STYLE_MAPS.shadow['md']} {!isDisabled ? inverseActiveClass : 'cursor-not-allowed opacity-40'}"
+						style="width: {ringItemSize}px; height: {ringItemSize}px; left: {pos.x}px; top: {pos.y}px; transform: translate(-50%, -50%);"
+						disabled={isDisabled}
+						onclick={() => handleRingActionClick(index, item)}
+						in:fly|global={{ duration: 250, x: -pos.x, y: -pos.y, opacity: 0, easing: cubicOut }}
+						out:fly|global={{ duration: 150, x: -pos.x, y: -pos.y, opacity: 0, easing: cubicOut }}
+					>
+						<Icon
+							{...item.icon}
+							state={iconState}
+							injClass={item.icon.injClass || ''}
+						/>
+					</button>
+				{/each}
+			</div>
+		{/if}
+	{:else}
+		<!-- 普通布局 Normal layout -->
+		<div
+			bind:this={panelRef}
+			class="fixed z-50 inline-block overflow-hidden border {inversePanelClass} {inlineRadius
+				? STYLE_MAPS.radius[inlineRadius] || 'rounded-(--radius-box)'
+				: 'rounded-(--radius-box)'} {STYLE_MAPS.shadow[inlineShadow] || STYLE_MAPS.shadow['md']}"
+			style="top: {inlinePosition.top}px; left: {inlinePosition.left}px; transform-origin: {transformOrigin()}; {positionReady ? '' : 'visibility: hidden;'}"
+			in:scale|global={{ duration: 200, start: 0.5, opacity: 0, easing: cubicOut }}
+			out:scale|global={{ duration: 150, start: 0.5, opacity: 0, easing: cubicOut }}
+		>
+			{#if title}
+				<div
+					class="flex h-10 flex-col justify-center whitespace-nowrap border-b px-4 text-xs {inverseTitleClass} {STYLE_MAPS
+						.titleAlign[titleAlign] || STYLE_MAPS.titleAlign['left']}"
+				>
+					{title}
+				</div>
+			{/if}
+			<div class="{actionContainerClass}">
+				{#each actions as item, index (index)}
+					{@const isDisabled = item.disabled}
+					{@const itemState = item.style || 'normal'}
+					{@const itemStateClass = STYLE_MAPS.state[itemState] || STYLE_MAPS.state.normal}
+					{@const iconState = item.icon?.state || (itemState !== 'normal' ? itemState : undefined)}
+					<button
+						class="{!isDisabled ? `${inverseActiveClass} ` : 'cursor-not-allowed opacity-40 '}transition-all flex items-center gap-2 whitespace-nowrap px-4 {STYLE_MAPS
+							.align[align] || STYLE_MAPS.align['center']} {actionLayoutClass}"
+						disabled={isDisabled}
+						onclick={() => handleActionClick(index, item)}
+					>
+						{#if item.icon}
+							<Icon
+								{...item.icon}
+								state={iconState}
+								injClass={item.icon.injClass || ''}
+							/>
+						{:else if item.showImg}
+							<div class="h-6 w-6 overflow-hidden {(item.imgRadius && STYLE_MAPS.imgRadius[item.imgRadius]) || 'rounded-full'}">
+								<img class="h-full w-full object-cover" src={item.imgSrc} alt="" />
+							</div>
+						{/if}
+						<div class="{itemStateClass} flex flex-col justify-center {item.desc ? ' h-10' : ' h-12'}">
+							{item.content}
+							{#if item.desc}
+								<div class="text-xs {inverseDescClass}">
+									{item.desc}
+								</div>
+							{/if}
+						</div>
+					</button>
+					{#if layout === 'v' && index !== actions.length - 1}
+						<div class="mx-2 h-px {inverseDividerClass}"></div>
+					{/if}
+				{/each}
+			</div>
+			{#if showCancel}
+				<div class="h-px {inverseDividerClass}"></div>
+				<button
+					class="flex h-12 items-center whitespace-nowrap px-4 transition-all {inverseActiveClass} {STYLE_MAPS.align[align] ||
+						STYLE_MAPS.align['center']}"
+					onclick={handleCancel}
+				>
+					{cancelText}
+				</button>
+			{/if}
+		</div>
+	{/if}
+{/if}

+ 33 - 10
packages/stdf/src/lib/components/actionSheet/ActionSheet.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 	import { getContext } from 'svelte';
 	import Popup from '../popup/Popup.svelte';
+	import Icon from '../icon/Icon.svelte';
 	import type { ActionSheetProps, ActionProps } from '../../types/index.js';
 	import { zh_CN, type LangProps } from '../../lang/index.js';
 
@@ -31,10 +32,21 @@
 		state: {
 			normal: ' ',
 			theme: ' text-primary dark:text-dark ',
-			danger: ' text-error ',
-			disabled: ' text-black/20 dark:text-white/20 '
+			success: ' text-success ',
+			warning: ' text-warning ',
+			error: ' text-error ',
+			info: ' text-info '
+		},
+		imgRadius: {
+			none: 'rounded-none',
+			xs: 'rounded-xs',
+			sm: 'rounded-sm',
+			md: 'rounded-md',
+			lg: 'rounded-lg',
+			xl: 'rounded-xl',
+			'2xl': 'rounded-2xl',
+			full: 'rounded-full'
 		},
-		imgRadius: { none: 'rounded-none', sm: 'rounded-sm', full: 'rounded-full', lg: 'rounded-lg' },
 		align: { left: 'justify-start pl-4', center: 'justify-center', right: 'justify-end pr-4' }
 	};
 
@@ -61,7 +73,7 @@
 	// 处理选项点击事件
 	// handle option click event
 	const handleActionClick = (index: number, item: ActionProps) => {
-		if (item.style === 'disabled') return;
+		if (item.disabled) return;
 		onclickAction?.(index, item);
 		if (actionClosable) {
 			visible = false;
@@ -81,22 +93,33 @@
 	{/if}
 	<div>
 		{#each actions as item, index (index)}
+			{@const isDisabled = item.disabled}
+			{@const itemState = item.style || 'normal'}
+			{@const itemStateClass = STYLE_MAPS.state[itemState] || STYLE_MAPS.state.normal}
+			{@const iconState = item.icon?.state || (itemState !== 'normal' ? itemState : undefined)}
 			<button
-				class="{item.style !== 'disabled'
+				class="{!isDisabled
 					? 'active:scale-90 '
-					: 'cursor-not-allowed '}transition-all flex w-full items-center gap-2 {STYLE_MAPS.align[align] || STYLE_MAPS.align['center']}"
-				disabled={item.style === 'disabled'}
+					: 'cursor-not-allowed opacity-40 '}transition-all flex w-full items-center gap-2 {STYLE_MAPS.align[align] || STYLE_MAPS.align['center']}"
+				disabled={isDisabled}
 				onclick={() => handleActionClick(index, item)}
 			>
-				{#if item.showImg}
+				{#if item.icon}
+					<Icon
+						{...item.icon}
+						state={iconState}
+						injClass={item.icon.injClass || ''}
+					/>
+				{:else if item.showImg}
 					<div class="h-6 w-6 overflow-hidden {(item.imgRadius && STYLE_MAPS.imgRadius[item.imgRadius]) || 'rounded-full'}">
 						<img class="h-full w-full object-cover" src={item.imgSrc} alt="" />
 					</div>
 				{/if}
 				<div>
 					<div
-						class="truncate text-center font-bold {(item.style && STYLE_MAPS.state[item.style]) ||
-							STYLE_MAPS.state.normal} flex flex-col justify-center {item.desc ? ' h-10' : ' h-14'}"
+						class="truncate text-center font-bold {itemStateClass} flex flex-col justify-center {item.desc
+							? ' h-10'
+							: ' h-14'}"
 					>
 						{item.content}
 					</div>

+ 259 - 0
packages/stdf/src/lib/components/alert/Alert.svelte

@@ -0,0 +1,259 @@
+<script lang="ts">
+	import { fly, slide, fade, scale, blur } from 'svelte/transition';
+	import * as eases from 'svelte/easing';
+	import Card from '../card/Card.svelte';
+	import Icon from '../icon/Icon.svelte';
+	import type { AlertProps } from '../../types/index.js';
+
+	let {
+		visible = $bindable(false),
+		title = '',
+		message = '',
+		duration = 3000,
+		position = 'top',
+		py = '20',
+		type = null,
+		showIcon = true,
+		icon = {},
+		closable = true,
+		inverse = true,
+		card = {},
+		transitionType = 'fly',
+		transitionParams = {},
+		outDuration = 300,
+		easeType = 'cubicOut',
+		easeOutType = 'cubicOut',
+		zIndex = 1000,
+		clickable = true,
+		injClass = '',
+		children,
+		onclose
+	}: AlertProps = $props();
+
+	let closingBySelf = $state(false);
+
+	// 自动关闭
+	// Auto close
+	$effect(() => {
+		if (visible && duration > 0) {
+			const timer = setTimeout(() => {
+				requestClose();
+			}, duration);
+			return () => clearTimeout(timer);
+		}
+	});
+
+	// 关闭按钮点击
+	// Close button click
+	const handleClose = () => {
+		requestClose();
+	};
+
+	const requestClose = () => {
+		if (!visible || closingBySelf) return;
+		closingBySelf = true;
+		visible = false;
+		if (!transitionType || outDuration <= 0) {
+			onclose?.();
+			closingBySelf = false;
+		}
+	};
+
+	const handleOutroEnd = () => {
+		if (!closingBySelf) return;
+		onclose?.();
+		closingBySelf = false;
+	};
+
+	// 位置样式
+	// Position style
+	const positionClass = {
+		top: 'top-0 left-0 right-0',
+		bottom: 'bottom-0 left-0 right-0'
+	};
+
+	// 距离样式
+	// Padding style
+	const pyClass = {
+		'0': 'py-0',
+		'10': 'py-10',
+		'20': 'py-5',
+		'40': 'py-10',
+		'60': 'py-15',
+		'80': 'py-20'
+	};
+
+	// 类型图标颜色
+	// Type icon color
+	const typeIconClass: Record<string, string> = {
+		success: 'text-success',
+		error: 'text-error',
+		warning: 'text-warning',
+		info: 'text-info'
+	};
+
+	// 进入过渡参数
+	// In transition params
+	const getInTransitionParams = () => {
+		const params: Record<string, unknown> = { duration: 300, easing: eases[easeType], ...transitionParams };
+		if (transitionType === 'fly' && params.y === undefined) {
+			params.y = position === 'top' ? -100 : 100;
+		}
+		return params;
+	};
+
+	// 退出过渡参数
+	// Out transition params
+	const getOutTransitionParams = () => {
+		const params: Record<string, unknown> = { ...transitionParams, duration: outDuration, easing: eases[easeOutType] };
+		if (transitionType === 'fly' && params.y === undefined) {
+			params.y = position === 'top' ? -100 : 100;
+		}
+		return params;
+	};
+
+	// 反转色样式(Tailwind safelist: bg-bg-surface-dark dark:bg-bg-surface text-text-dark dark:text-text-primary text-text-dark/70 dark:text-text-primary/70 text-text-dark/50 dark:text-text-primary/50 hover:bg-text-dark/5 dark:hover:bg-text-primary/5 active:bg-text-dark/10 dark:active:bg-text-primary/10)
+	// Inverse color styles
+	const inverseBgClass = $derived(inverse ? 'bg-bg-surface-dark dark:bg-bg-surface' : '');
+	const inverseTextClass = $derived(inverse ? 'text-text-dark dark:text-text-primary' : '');
+	const inverseMessageClass = $derived(inverse ? 'text-text-dark/70 dark:text-text-primary/70' : 'text-black/70 dark:text-white/70');
+	const inverseCloseClass = $derived(
+		inverse
+			? 'text-text-dark/50 dark:text-text-primary/50 hover:bg-text-dark/5 dark:hover:bg-text-primary/5 active:bg-text-dark/10 dark:active:bg-text-primary/10'
+			: 'text-black/50 dark:text-white/50 hover:bg-black/5 dark:hover:bg-white/5 active:bg-black/10 dark:active:bg-white/10'
+	);
+</script>
+
+{#snippet alertContent()}
+	<div class="pointer-events-auto {inverseBgClass} {inverseTextClass} rounded-(--radius-box)">
+		<Card shadow="lg" mx="0" my="0" bg={inverse ? 'none' : 'surface'} {...card}>
+			<div class="flex items-start gap-3">
+				<!-- 类型图标 Type icon -->
+				{#if showIcon && type}
+					<div class="shrink-0 {typeIconClass[type]}">
+						{#if type === 'success'}
+							<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+								<path
+									d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM11.0026 16L18.0737 8.92893L16.6595 7.51472L11.0026 13.1716L8.17421 10.3431L6.75999 11.7574L11.0026 16Z"
+								/>
+							</svg>
+						{:else if type === 'error'}
+							<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+								<path
+									d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"
+								/>
+							</svg>
+						{:else if type === 'warning'}
+							<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+								<path
+									d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM11 15V17H13V15H11ZM11 7V13H13V7H11Z"
+								/>
+							</svg>
+						{:else if type === 'info'}
+							<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+								<path
+									d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM11 7V9H13V7H11ZM11 11V17H13V11H11Z"
+								/>
+							</svg>
+						{/if}
+					</div>
+				{:else if showIcon && icon && Object.keys(icon).length > 0}
+					<div class="shrink-0">
+						<Icon size={24} {...icon} />
+					</div>
+				{/if}
+
+				<!-- 内容区域 Content area -->
+				<div class="min-w-0 flex-1">
+					{#if children}
+						{@render children()}
+					{:else}
+						{#if title}
+							<div class="font-medium">{title}</div>
+						{/if}
+						{#if message}
+							<div class="text-sm {inverseMessageClass} {title ? 'mt-1' : ''}">{message}</div>
+						{/if}
+					{/if}
+				</div>
+
+				<!-- 关闭按钮 Close button -->
+				{#if closable}
+					<button
+						type="button"
+						class="-m-1 shrink-0 rounded-full p-1 transition-colors {inverseCloseClass}"
+						onclick={handleClose}
+						aria-label="Close"
+					>
+						<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
+							<path
+								d="M12 10.5858L16.9497 5.63604L18.364 7.05025L13.4142 12L18.364 16.9497L16.9497 18.364L12 13.4142L7.05025 18.364L5.63604 16.9497L10.5858 12L5.63604 7.05025L7.05025 5.63604L12 10.5858Z"
+							/>
+						</svg>
+					</button>
+				{/if}
+			</div>
+		</Card>
+	</div>
+{/snippet}
+
+{#if visible}
+	{#if transitionType === 'scale'}
+		<div
+			class="fixed {positionClass[position]} {pyClass[py]} px-2 {clickable ? 'pointer-events-none' : ''} {injClass}"
+			style="z-index: {zIndex};"
+			in:scale|global={getInTransitionParams()}
+			out:scale|global={getOutTransitionParams()}
+			onoutroend={handleOutroEnd}
+		>
+			{@render alertContent()}
+		</div>
+	{:else if transitionType === 'fly'}
+		<div
+			class="fixed {positionClass[position]} {pyClass[py]} px-2 {clickable ? 'pointer-events-none' : ''} {injClass}"
+			style="z-index: {zIndex};"
+			in:fly|global={getInTransitionParams()}
+			out:fly|global={getOutTransitionParams()}
+			onoutroend={handleOutroEnd}
+		>
+			{@render alertContent()}
+		</div>
+	{:else if transitionType === 'slide'}
+		<div
+			class="fixed {positionClass[position]} {pyClass[py]} px-2 {clickable ? 'pointer-events-none' : ''} {injClass}"
+			style="z-index: {zIndex};"
+			in:slide|global={getInTransitionParams()}
+			out:slide|global={getOutTransitionParams()}
+			onoutroend={handleOutroEnd}
+		>
+			{@render alertContent()}
+		</div>
+	{:else if transitionType === 'fade'}
+		<div
+			class="fixed {positionClass[position]} {pyClass[py]} px-2 {clickable ? 'pointer-events-none' : ''} {injClass}"
+			style="z-index: {zIndex};"
+			in:fade|global={getInTransitionParams()}
+			out:fade|global={getOutTransitionParams()}
+			onoutroend={handleOutroEnd}
+		>
+			{@render alertContent()}
+		</div>
+	{:else if transitionType === 'blur'}
+		<div
+			class="fixed {positionClass[position]} {pyClass[py]} px-2 {clickable ? 'pointer-events-none' : ''} {injClass}"
+			style="z-index: {zIndex};"
+			in:blur|global={getInTransitionParams()}
+			out:blur|global={getOutTransitionParams()}
+			onoutroend={handleOutroEnd}
+		>
+			{@render alertContent()}
+		</div>
+	{:else}
+		<div
+			class="fixed {positionClass[position]} {pyClass[py]} px-2 {clickable ? 'pointer-events-none' : ''} {injClass}"
+			style="z-index: {zIndex};"
+		>
+			{@render alertContent()}
+		</div>
+	{/if}
+{/if}

+ 29 - 13
packages/stdf/src/lib/components/asyncPicker/AsyncPicker.svelte

@@ -27,6 +27,7 @@
 		prevText = asyncPickerLang.defaultPrev,
 		showSelected = false,
 		selectedText = asyncPickerLang.defaultSelected,
+		height = 30,
 		popup = {},
 		loading = {},
 		oncancel,
@@ -36,6 +37,10 @@
 		onclose
 	}: AsyncPickerProps = $props();
 
+	// 是否使用弹出层,当 popup 为 null 时不使用
+	// Whether to use popup, when popup is null, do not use
+	let usePopup = $derived(popup !== null);
+
 	// 用于存储当前选定的所有项和索引
 	// Used to store all selected items and indexes
 	let items: Record<string, unknown>[] = $state([]);
@@ -93,14 +98,8 @@
 	let isLoading = $derived(data.length === 0);
 </script>
 
-<Popup
-	bind:visible
-	size={0}
-	maskClosable
-	transitionDistance={(showRow === 3 ? 64 : showRow === 5 ? 48 : 32) * showRow + 41 + (showSelected ? 32 : 0)}
-	{...popup}
->
-	<div class="flex items-center justify-between border-b border-black/10 bg-white dark:border-white/20 dark:bg-black">
+{#snippet asyncPickerContent()}
+	<div class="flex items-center justify-between border-b border-black/10 bg-bg-surface dark:border-white/20 dark:bg-bg-surface-dark">
 		<button
 			class="h-10 px-4 leading-10 text-black/60 dark:text-white/60"
 			onclick={() => {
@@ -132,7 +131,7 @@
 		</button>
 	</div>
 	{#if showSelected}
-		<div class="flex w-full gap-3 overflow-x-hidden bg-white px-4 dark:bg-black">
+		<div class="flex w-full gap-3 overflow-x-hidden bg-bg-surface px-4 dark:bg-bg-surface-dark">
 			<div class="h-8 flex-none text-center text-sm leading-8 text-black/60 dark:text-white/60">
 				{selectedText}
 			</div>
@@ -149,14 +148,17 @@
 			{/each}
 		</div>
 	{/if}
-	<div class="bg-white dark:bg-black">
+	<div class="bg-bg-surface dark:bg-bg-surface-dark">
 		<div class="truncate">
 			{#if isLoading}
-				<div class="flex items-center justify-center" style="height:{(showRow === 3 ? 64 : showRow === 5 ? 48 : 32) * showRow}px">
+				<div
+					class="flex items-center justify-center"
+					style="height:{usePopup ? (showRow === 3 ? 64 : showRow === 5 ? 48 : 32) * showRow : (window.innerHeight * height) / 100}px"
+				>
 					<Loading width="28" height="8" type="1_16" theme {...loading} />
 				</div>
 			{:else}
-				<div>
+				<div style={usePopup ? '' : `height:${(window.innerHeight * height) / 100}px`}>
 					<ScrollRadio
 						data={data as Record<string, string>[]}
 						{showRow}
@@ -169,4 +171,18 @@
 			{/if}
 		</div>
 	</div>
-</Popup>
+{/snippet}
+
+{#if usePopup}
+	<Popup
+		bind:visible
+		size={0}
+		maskClosable
+		transitionDistance={(showRow === 3 ? 64 : showRow === 5 ? 48 : 32) * showRow + 41 + (showSelected ? 32 : 0)}
+		{...popup}
+	>
+		{@render asyncPickerContent()}
+	</Popup>
+{:else}
+	{@render asyncPickerContent()}
+{/if}

+ 3 - 13
packages/stdf/src/lib/components/avatar/Avatar.svelte

@@ -1,13 +1,14 @@
 <script lang="ts">
 	import Icon from '../icon/Icon.svelte';
 	import type { AvatarProps } from '$lib/types/index.js';
+	import { radiusObj } from '../utils/index.js';
 
 	let {
 		image = '',
 		alt = '',
 		icon = {},
 		altSize = 'md',
-		radius = 'sm',
+		radius = '',
 		size = 'base',
 		imgSize = 'l',
 		line = 'none',
@@ -15,17 +16,6 @@
 		onclick
 	}: AvatarProps = $props();
 
-	// 圆角风格样式
-	// radius style
-	const radiusObj = {
-		none: 'rounded-none',
-		sm: 'rounded-sm',
-		xl: 'rounded-xl',
-		'2xl': 'rounded-2xl',
-		'3xl': 'rounded-3xl',
-		full: 'rounded-full'
-	};
-
 	// 头像框大小样式
 	// avatar size style
 	const sizeObj = {
@@ -63,7 +53,7 @@
 <button
 	class="bg-primary-200 dark:bg-dark-200 flex flex-col items-center justify-center overflow-hidden relative{lineObj[line] || ''} {sizeObj[
 		size
-	] || sizeObj.md} {radiusObj[radius] || radiusObj.sm} {injClass}"
+	] || sizeObj.md} {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {injClass}"
 	onclick={() => onclick && onclick()}
 >
 	{#if image === '' && alt === ''}

+ 32 - 48
packages/stdf/src/lib/components/avatar/Avatars.svelte → packages/stdf/src/lib/components/avatar/AvatarGroup.svelte

@@ -1,30 +1,21 @@
 <script lang="ts">
 	import Avatar from './Avatar.svelte';
-	import type { AvatarsProps } from '../../types/index.js';
+	import type { AvatarGroupProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
 
 	let {
 		data = [],
-		radius = 'full',
+		radius = '',
 		size = 'base',
-		compact = 5,
+		compact = 4,
 		lineWidth = '3',
 		reverse = false,
 		max = 10,
 		top = 'totle',
 		injClass = '',
 		onclick
-	}: AvatarsProps = $props();
+	}: AvatarGroupProps = $props();
 
-	// 圆角风格样式
-	// radius style
-	const radiusObj = {
-		none: 'rounded-none',
-		sm: 'rounded-sm',
-		xl: 'rounded-xl',
-		'2xl': 'rounded-2xl',
-		'3xl': 'rounded-3xl',
-		full: 'rounded-full'
-	};
 	// 边框粗细样式
 	// border width style
 	const lineWidthClass = {
@@ -60,28 +51,25 @@
 		'2xl': 'text-8xl'
 	};
 
-	const dataInner = data.slice(0, max);
+	const dataInner = $derived(data.slice(0, max));
 </script>
 
-<button class="flex" onclick={() => onclick && onclick()}>
-	{#if reverse}
-		<div
-			class="relative {lineWidthClass[lineWidth] || lineWidthClass[2]} ring-primaryWhite dark:ring-darkBlack {radiusObj[radius] ||
-				radiusObj.sm} {injClass}"
-			style="z-index:{dataInner.length}"
-		>
+<div class="w-full overflow-hidden">
+	<button class="flex" onclick={() => onclick && onclick()}>
+		{#if reverse}
+			<div
+				class="relative {lineWidthClass[lineWidth] || lineWidthClass[2]} ring-bg-base dark:ring-bg-base-dark {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {injClass}"
+				style="z-index:{dataInner.length}"
+			>
 			{#if top === 'totle'}
 				<div
-					class="bg-primary-200 dark:bg-dark-200 text-primary-950 dark:text-dark-950 flex flex-col justify-center overflow-hidden text-center {radiusObj[
-						radius
-					] || radiusObj.sm} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
+					class="bg-primary-200 dark:bg-dark-200 text-primary-950 dark:text-dark-950 flex flex-col justify-center overflow-hidden text-center {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
 				>
 					+{data.length}
 				</div>
 			{:else if top === 'add'}
 				<div
-					class="bg-primary-200 dark:bg-dark-200 flex flex-col justify-center overflow-hidden text-center {radiusObj[radius] ||
-						radiusObj.sm} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
+					class="bg-primary-200 dark:bg-dark-200 flex flex-col justify-center overflow-hidden text-center {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
 				>
 					<div class="mx-auto">
 						<svg
@@ -102,33 +90,28 @@
 			{/if}
 		</div>
 	{/if}
-	{#each dataInner as item, i (item.image)}
-		<div
-			class="relative {lineWidthClass[lineWidth] || lineWidthClass[2]} ring-primaryWhite dark:ring-darkBlack {radiusObj[radius] ||
-				radiusObj.sm} {injClass}"
-			style="transform: translateX({-compact * 10 * i - (reverse ? 50 : 0)}%);z-index:{reverse ? dataInner.length - 1 - i : i}"
-		>
-			<Avatar {radius} {size} {...item} />
-		</div>
-	{/each}
-	{#if !reverse}
-		<div
-			class="relative {lineWidthClass[lineWidth] || lineWidthClass[2]} ring-primaryWhite dark:ring-darkBlack {radiusObj[radius] ||
-				radiusObj.sm} {injClass}"
-			style="transform: translateX(-{compact * 10 * dataInner.length}%);z-index:{dataInner.length}"
-		>
+		{#each dataInner as item, i (item.image)}
+			<div
+				class="relative {lineWidthClass[lineWidth] || lineWidthClass[2]} ring-bg-base dark:ring-bg-base-dark {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {injClass}"
+				style="transform: translateX({-compact * 10 * i - (reverse ? 50 : 0)}%); z-index: {reverse ? dataInner.length - 1 - i : i}"
+			>
+				<Avatar {radius} {size} {...item} />
+			</div>
+		{/each}
+		{#if !reverse}
+			<div
+				class="relative {lineWidthClass[lineWidth] || lineWidthClass[2]} ring-bg-base dark:ring-bg-base-dark {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {injClass}"
+				style="transform: translateX(-{compact * 10 * dataInner.length}%); z-index: {dataInner.length}"
+			>
 			{#if top === 'totle'}
 				<div
-					class="bg-primary-200 dark:bg-dark-200 text-primary-950 dark:text-dark-950 flex flex-col justify-center overflow-hidden text-center {radiusObj[
-						radius
-					] || radiusObj.sm} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
+					class="bg-primary-200 dark:bg-dark-200 text-primary-950 dark:text-dark-950 flex flex-col justify-center overflow-hidden text-center {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
 				>
 					+{data.length}
 				</div>
 			{:else if top === 'add'}
 				<div
-					class="bg-primary-200 dark:bg-dark-200 flex flex-col justify-center overflow-hidden text-center {radiusObj[radius] ||
-						radiusObj.sm} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
+					class="bg-primary-200 dark:bg-dark-200 flex flex-col justify-center overflow-hidden text-center {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {sizeObj[size] || sizeObj.md} {textSizeObj[size] || textSizeObj.base}"
 				>
 					<div class="mx-auto">
 						<svg
@@ -149,4 +132,5 @@
 			{/if}
 		</div>
 	{/if}
-</button>
+	</button>
+</div>

+ 15 - 10
packages/stdf/src/lib/components/badge/Badge.svelte

@@ -1,8 +1,10 @@
 <script lang="ts">
 	import type { BadgeProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
+
 	let {
 		text = '',
-		radius = 'full',
+		radius = '',
 		isLeft = false,
 		isShow = true,
 		offsetY = 0,
@@ -12,24 +14,27 @@
 		children
 	}: BadgeProps = $props();
 
-	// 圆角风格样式
-	// radius style
-	const radiusObj = {
-		full: ' rounded-full',
-		sm: ' rounded-sm',
-		none: ' rounded-none',
+	// 叶形圆角样式
+	// leaf radius style
+	const leafRadiusObj = {
 		leftLeaf: ' rounded-full rounded-br-none',
 		rightLeaf: ' rounded-full rounded-bl-none'
 	};
 
 	// 通过 radius 配置圆角风格或叶形
 	// set radius style or leaf style by radius
-	const radiusClass = radius === 'leaf' ? (isLeft ? radiusObj.leftLeaf : radiusObj.rightLeaf) : radiusObj[radius] || radiusObj.full;
+	const radiusClass = $derived(
+		radius === 'leaf'
+			? isLeft
+				? leafRadiusObj.leftLeaf
+				: leafRadiusObj.rightLeaf
+			: ` ${radius ? radiusObj[radius] : 'rounded-(--radius-small)'}`
+	);
 </script>
 
 {#if isInner}
 	<div
-		class="px-1 text-xs text-white whitespace-nowrap{radiusClass}{text === ''
+		class="whitespace-nowrap px-1 text-xs text-white {radiusClass}{text === ''
 			? ' h-3 w-3'
 			: ' '} text-center leading-4 transition-all bg-[red]{injClass === '' ? '' : ` ${injClass}`}"
 		style="transform:{isShow ? ' scale(1)' : ' scale(0)'}"
@@ -42,7 +47,7 @@
 		<div
 			class="absolute{text === '' ? ' -top-1.5' : ' -top-2'}{isLeft
 				? ' left-0'
-				: ' right-0'} px-1 text-xs text-white whitespace-nowrap{radiusClass}{text === ''
+				: ' right-0'} whitespace-nowrap px-1 text-xs text-white {radiusClass}{text === ''
 				? ' h-3 w-3'
 				: ' '} text-center leading-4 transition-all bg-[red]{injClass === '' ? '' : ` ${injClass}`}"
 			style="transform: translateX(calc({isLeft ? '-' : ''}50% {isLeft ? '+' : '-'} {offsetX}px)) translateY({offsetY}px){isShow

+ 56 - 67
packages/stdf/src/lib/components/bottomSheet/BottomSheet.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { onMount, getContext } from 'svelte';
 	import { fly } from 'svelte/transition';
-	import { throttleWithRAF } from '../utils/index.js';
+	import { throttleWithRAF, radiusObj } from '../utils/index.js';
 	import Mask from '../mask/Mask.svelte';
 	import { zh_CN, type LangProps } from '../../lang/index.js';
 	import type { BottomSheetProps } from '../../types/index.js';
@@ -26,7 +26,8 @@
 		stayHeightList = [10, 50, 90],
 		stayHeightIndex = 1,
 		closeHeight = 0,
-		radius = 'full',
+		radius = '',
+		iconRadius = '',
 		children,
 		onheightChange,
 		onclickMask,
@@ -36,7 +37,7 @@
 
 	// 固定高度
 	// stay height
-	let stayHeight = stayHeightList[stayHeightIndex] || stayHeightList[stayHeightList.length - 1];
+	let stayHeight = $derived(stayHeightList[stayHeightIndex] || stayHeightList[stayHeightList.length - 1]);
 
 	// 此时是否正在滑动
 	// is sliding or not now
@@ -52,7 +53,10 @@
 
 	// 滑动开始距离顶部高度,%
 	// start distance from top, %
-	let startTop = $state(100 - stayHeight);
+	let startTop = $state(0);
+	$effect(() => {
+		startTop = 100 - stayHeight;
+	});
 
 	// 滑动距离,%
 	// move distance, %
@@ -75,50 +79,58 @@
 
 	// 如果 stayHeightList 不是数组,或者元素不是正数,或者元素不是 0-100 之间的数,或者元素不是整数,给出警告。
 	// If stayHeightList is not an array, or the element is not a positive number, or the element is not a number between 0 and 100, or the element is not an integer, give a warning.
-	if (
-		!Array.isArray(stayHeightList) ||
-		stayHeightList.some((item) => typeof item !== 'number' || item < 0 || item > 100 || item % 1 !== 0)
-	) {
-		console.error(
-			'[STDF BottomSheet error]stayHeightList 必须是一个 0-100 之间的正整数数组。(stayHeightList must be an array of positive integers between 0 and 100)'
-		);
-	}
+	$effect(() => {
+		if (
+			!Array.isArray(stayHeightList) ||
+			stayHeightList.some((item) => typeof item !== 'number' || item < 0 || item > 100 || item % 1 !== 0)
+		) {
+			console.error(
+				'[STDF BottomSheet error]stayHeightList 必须是一个 0-100 之间的正整数数组。(stayHeightList must be an array of positive integers between 0 and 100)'
+			);
+		}
 
-	// 如果 stayHeightList 元素不是递增的,给出警告。
-	// If the elements of stayHeightList are not increasing, give a warning.
-	if (stayHeightList.some((item, index) => index > 0 && item <= stayHeightList[index - 1])) {
-		console.error(
-			'[STDF BottomSheet error]stayHeightList 数组元素必须是升序排列。(stayHeightList array elements must be in ascending order)'
-		);
-	}
+		// 如果 stayHeightList 元素不是递增的,给出警告。
+		// If the elements of stayHeightList are not increasing, give a warning.
+		if (stayHeightList.some((item, index) => index > 0 && item <= stayHeightList[index - 1])) {
+			console.error(
+				'[STDF BottomSheet error]stayHeightList 数组元素必须是升序排列。(stayHeightList array elements must be in ascending order)'
+			);
+		}
 
-	// 如果 stayHeightIndex 超出 stayHeightList 长度给出警告。
-	// If stayHeightIndex exceeds the length of stayHeightList, give a warning.
-	if (stayHeightIndex > stayHeightList.length - 1) {
-		console.warn(
-			'[STDF BottomSheet warn]stayHeightIndex 超出 stayHeightList 长度,将使用 stayHeightList 最后一个值。(stayHeightIndex exceeds the length of stayHeightList, the last value of stayHeightList will be used.)'
-		);
-	}
+		// 如果 stayHeightIndex 超出 stayHeightList 长度给出警告。
+		// If stayHeightIndex exceeds the length of stayHeightList, give a warning.
+		if (stayHeightIndex > stayHeightList.length - 1) {
+			console.warn(
+				'[STDF BottomSheet warn]stayHeightIndex 超出 stayHeightList 长度,将使用 stayHeightList 最后一个值。(stayHeightIndex exceeds the length of stayHeightList, the last value of stayHeightList will be used.)'
+			);
+		}
 
-	// 如果 closeHeight 大于 stayHeightList 最小值给出警告。
-	// If closeHeight is greater than the minimum value of stayHeightList, give a warning.
-	if (closeHeight > stayHeightList[0]) {
-		console.warn(
-			'[STDF BottomSheet warn]closeHeight 大于 stayHeightList 最小值,closeHeight 将失效。(closeHeight is greater than the minimum value of stayHeightList, closeHeight will be invalid.)'
-		);
-	}
+		// 如果 closeHeight 大于 stayHeightList 最小值给出警告。
+		// If closeHeight is greater than the minimum value of stayHeightList, give a warning.
+		if (closeHeight > stayHeightList[0]) {
+			console.warn(
+				'[STDF BottomSheet warn]closeHeight 大于 stayHeightList 最小值,closeHeight 将失效。(closeHeight is greater than the minimum value of stayHeightList, closeHeight will be invalid.)'
+			);
+		}
+	});
 
 	// 标题对齐方式
 	// title align
 	const titleAlignClass = { left: ' text-left', center: ' text-center', right: ' text-right' };
 
-	// 窗口圆角风格
-	// window radius style
-	const windowRadiusClass = { none: ' rounded-none', middle: ' rounded-t-lg', full: ' rounded-t-2xl' };
+	// 窗口圆角风格(顶部圆角)
+	// window radius style (top radius)
+	const windowRadiusClass = {
+		none: ' rounded-t-none',
+		sm: ' rounded-t-sm',
+		md: ' rounded-t-md',
+		lg: ' rounded-t-lg',
+		xl: ' rounded-t-xl',
+		'2xl': ' rounded-t-2xl',
+		'3xl': ' rounded-t-3xl',
+		'4xl': ' rounded-t-4xl'
+	};
 
-	// 图标圆角风格
-	// icon radius style
-	const iconRadiusClass = { none: ' rounded-none', middle: ' rounded-sm', full: ' rounded-full' };
 
 	// 滑动开始
 	// start sliding
@@ -204,29 +216,6 @@
 		onback?.();
 	};
 
-	// 滚动时禁止 body 滚动
-	// Disable body scrolling when scrolling
-	$effect(() => {
-		if (visible) {
-			//当 visible 为 true 时,禁止 body 滚动
-			//When visible is true, body scrolling is disabled
-			const top = document.documentElement.scrollTop || document.body.scrollTop;
-			document.body.style.cssText += `
-            position: fixed;
-            width: 100vw;
-            left: 0;
-            top: ${-top}px;
-            touch-action:none;
-        `;
-		} else {
-			const top = document.body.style.top;
-			document.body.style.cssText += `
-            position: static;
-            touch-action:auto;
-        `;
-			window.scrollTo(0, Math.abs(parseFloat(top)));
-		}
-	});
 	onMount(() => {
 		if (visible && scrollTopDom) {
 			// 滚动内容高度
@@ -243,7 +232,7 @@
 <div class="pointer-events-none fixed inset-0 flex h-screen w-screen flex-col justify-end px-0" style="z-index:{zIndex};">
 	{#if visible}
 		<div
-			class="fixed w-screen bg-white dark:bg-gray-950{windowRadiusClass[radius] || windowRadiusClass['full']} pointer-events-auto{isTouch
+			class="fixed w-screen bg-bg-overlay dark:bg-bg-overlay-dark {radius ? windowRadiusClass[radius] : 'rounded-t-(--radius-box)'} pointer-events-auto{isTouch
 				? ''
 				: ' transition-all duration-300'}"
 			style="height:{stayHeightList[stayHeightList.length - 1]}%;top:{currentTop}%"
@@ -265,11 +254,11 @@
 				bind:this={scrollTopDom}
 				class="cursor-move touch-none select-none py-1"
 			>
-				<div class="h-1 w-8 bg-black/20 dark:bg-white/30 mx-auto{radius === 'none' ? ' rounded-none' : ' rounded-full'}"></div>
+				<div class="h-1 w-8 bg-black/20 dark:bg-white/30 mx-auto rounded-full"></div>
 				<div class="flex items-center justify-between gap-2 px-3 py-1">
 					{#if showBackIcon}
 						<button
-							class="h-6 w-6 flex-none bg-black/5 dark:bg-white/10 text-center{iconRadiusClass[radius] || iconRadiusClass['full']}"
+							class="h-6 w-6 flex-none bg-black/5 dark:bg-white/10 text-center {iconRadius ? radiusObj[iconRadius] : 'rounded-(--radius-small)'}"
 							onclick={backFunc}
 							aria-label="back"
 						>
@@ -291,7 +280,7 @@
 						<!-- null -->
 					{:else if closeContent === 'closeIcon'}
 						<button
-							class="h-6 w-6 flex-none bg-black/5 dark:bg-white/10 text-center{iconRadiusClass[radius] || iconRadiusClass['full']}"
+							class="h-6 w-6 flex-none bg-black/5 dark:bg-white/10 text-center {iconRadius ? radiusObj[iconRadius] : 'rounded-(--radius-small)'}"
 							onclick={closeFunc}
 							aria-label="close"
 						>
@@ -310,7 +299,7 @@
 						</button>
 					{:else if closeContent === 'downIcon'}
 						<button
-							class="h-6 w-6 flex-none bg-black/5 dark:bg-white/10 text-center{iconRadiusClass[radius] || iconRadiusClass['full']}"
+							class="h-6 w-6 flex-none bg-black/5 dark:bg-white/10 text-center {iconRadius ? radiusObj[iconRadius] : 'rounded-(--radius-small)'}"
 							onclick={closeFunc}
 							aria-label="close"
 						>

+ 101 - 37
packages/stdf/src/lib/components/button/Button.svelte

@@ -2,18 +2,18 @@
 	import Icon from '../icon/Icon.svelte';
 	import Loading from '../loading/Loading.svelte';
 	import type { ButtonProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
 
 	let {
 		fill = 'base',
-		state = 'theme',
-		radius = 'sm',
+		state,
+		radius = '',
 		size = 'big',
-		line = 'solid',
+		border = 'solid',
 		injClass = '',
 		love = false,
 		heightOut = '2',
 		heightIn = '3',
-		group = false,
 		disabled = false,
 		customSize = false,
 		customWidth = 0,
@@ -29,33 +29,94 @@
 
 	let innerDisabled = $derived(disabled || (loading && disabledLoading));
 
-	// 状态样式
-	// State style
-	const stateObj = { theme: 'bg-primary dark:bg-dark', success: 'bg-success', warning: 'bg-warning', error: 'bg-error', info: 'bg-info' };
-
-	// 圆角风格样式
-	// Corner style style
-	const radiusObj = { none: 'rounded-none', sm: 'rounded-sm', xl: 'rounded-xl', '2xl': 'rounded-2xl', full: 'rounded-full' };
-
-	// 填充模式样式
-	// Fill mode style
-	const fillObj = {
-		base: '',
-		line: 'border border-black dark:border-white !text-black dark:!text-white',
-		lineLight: 'border border-black/20 dark:border-white/30 !text-black dark:!text-white',
-		lineTheme: 'border border-primary dark:border-dark !text-primary dark:!text-dark',
-		text: '!text-black dark:!text-white',
-		textTheme: '!text-primary dark:!text-dark',
-		colorLight: '!bg-black/5 dark:!bg-white/10 !text-black dark:!text-white'
+	// fill + state 组合样式映射
+	// fill + state combination style mapping
+
+	// base 模式:实心填充,跟随 state
+	// base mode: solid fill, follows state
+	const stateBaseObj: Record<string, string> = {
+		theme: 'bg-primary dark:bg-dark',
+		success: 'bg-success',
+		warning: 'bg-warning',
+		error: 'bg-error',
+		info: 'bg-info'
+	};
+
+	// line 模式:无色边框(黑/白),不跟随 state
+	// line mode: colorless border (black/white), does not follow state
+	const fillLineClass = 'border border-text-primary dark:border-text-dark !text-text-primary dark:!text-text-dark';
+
+	// lineLight 模式:浅色无色边框,不跟随 state
+	// lineLight mode: light colorless border, does not follow state
+	const fillLineLightClass = 'border border-text-primary/20 dark:border-text-dark/30 !text-text-primary dark:!text-text-dark';
+
+	// lineState 模式:状态色边框,跟随 state
+	// lineState mode: state color border, follows state
+	const stateLineStateObj: Record<string, string> = {
+		theme: 'border border-primary dark:border-dark !text-primary dark:!text-dark',
+		success: 'border border-success !text-success',
+		warning: 'border border-warning !text-warning',
+		error: 'border border-error !text-error',
+		info: 'border border-info !text-info'
 	};
 
-	// 尺寸样式
-	// Size style
-	const sizeObj = { full: 'w-full', big: 'w-full', md: 'w-1/2', sm: 'w-1/4', auto: 'w-auto' };
+	// text 模式:无色纯文本(黑/白),不跟随 state
+	// text mode: colorless text (black/white), does not follow state
+	const fillTextClass = '!text-text-primary dark:!text-text-dark';
+
+	// textState 模式:状态色纯文本,跟随 state
+	// textState mode: state color text, follows state
+	const stateTextStateObj: Record<string, string> = {
+		theme: '!text-primary dark:!text-dark',
+		success: '!text-success',
+		warning: '!text-warning',
+		error: '!text-error',
+		info: '!text-info'
+	};
+
+	// colorLight 模式:浅色填充,默认浅灰,state 指定时跟随 state
+	// colorLight mode: light fill, default gray, follows state when specified
+	const stateColorLightObj: Record<string, string> = {
+		default: '!bg-black/10 dark:!bg-white/10 !text-black dark:!text-white',
+		theme: '!bg-primary/10 dark:!bg-dark/10 !text-primary dark:!text-dark',
+		success: '!bg-success/10 !text-success',
+		warning: '!bg-warning/10 !text-warning',
+		error: '!bg-error/10 !text-error',
+		info: '!bg-info/10 !text-info'
+	};
+
+	// 计算 fill + state 组合样式
+	// Calculate fill + state combination style
+	const fillStateClass = $derived.by(() => {
+		switch (fill) {
+			case 'base':
+				return stateBaseObj[state || 'theme'];
+			case 'line':
+				return fillLineClass;
+			case 'lineLight':
+				return fillLineLightClass;
+			case 'lineState':
+				return stateLineStateObj[state || 'theme'];
+			case 'text':
+				return fillTextClass;
+			case 'textState':
+				return stateTextStateObj[state || 'theme'];
+			case 'colorLight':
+				// colorLight 模式:未指定 state 时使用灰色,指定 state 时跟随 state
+				// colorLight mode: use gray when state not specified, follow state when specified
+				return state ? stateColorLightObj[state] : stateColorLightObj.default;
+			default:
+				return stateBaseObj[state || 'theme'];
+		}
+	});
+
+	// 尺寸样式(md/sm 根据视口宽度计算)
+	// Size style (md/sm calculated based on viewport width)
+	const sizeObj = { full: 'w-full', big: 'w-full', md: 'w-[50vw]', sm: 'w-[25vw]', auto: 'w-auto' };
 
 	// 边框风格样式
-	// Border style style
-	const lineObj = { solid: 'border-solid', dashed: 'border-dashed', dotted: 'border-dotted' };
+	// Border style
+	const borderObj = { solid: 'border-solid', dashed: 'border-dashed', dotted: 'border-dotted' };
 
 	// 按钮外部高度样式
 	// Button outer height style
@@ -67,11 +128,16 @@
 
 	// 是否块级元素
 	// Whether it is a block element
-	const block = (size === 'full' || size === 'big') && !customSize;
+	let block = $derived((size === 'full' || size === 'big') && !customSize);
 
-	// 文字颜色
-	// Text color
-	const textColor = state === 'theme' ? 'text-white dark:text-black' : 'text-white';
+	// 文字颜色(仅 base 模式需要反色文字)
+	// Text color (only base mode needs inverted text)
+	const textColor = $derived.by(() => {
+		if (fill === 'base') {
+			return state === 'theme' || !state ? 'text-text-on-primary dark:text-text-on-dark' : 'text-text-on-primary';
+		}
+		return '';
+	});
 </script>
 
 <div
@@ -81,12 +147,10 @@
 >
 	<button
 		{onclick}
-		class="inline-flex items-center justify-center gap-1 truncate {!group && !innerDisabled ? 'active:opacity-80' : ''} {heightInObj[
-			heightIn
-		] || heightInObj['3']} {sizeObj[size] || sizeObj.big} {textColor} {lineObj[line] || lineObj.solid} {radiusObj[radius] ||
-			radiusObj.sm} {fill === 'base' && (stateObj[state] || stateObj.theme)} {fillObj[fill] || fillObj.base} {injClass} {innerDisabled
-			? 'cursor-not-allowed opacity-50'
-			: ''}"
+		class="inline-flex items-center justify-center gap-1 truncate {!innerDisabled ? 'active:opacity-80' : ''} {heightInObj[heightIn] ||
+			heightInObj['3']} {sizeObj[size] || sizeObj.big} {textColor} {borderObj[border] || borderObj.solid} {radius
+			? radiusObj[radius]
+			: 'rounded-(--radius-form)'} {fillStateClass} {injClass} {innerDisabled ? 'cursor-not-allowed opacity-50' : ''}"
 		disabled={innerDisabled}
 		style={customSize ? `width:${customWidth}px;height:${customHeight}px;padding:0;` : ''}
 		{type}

+ 183 - 0
packages/stdf/src/lib/components/button/ButtonGroup.svelte

@@ -0,0 +1,183 @@
+<script lang="ts">
+	import Icon from '../icon/Icon.svelte';
+	import type { ButtonGroupProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
+
+	let {
+		items = [],
+		fill = 'base',
+		state,
+		radius = '',
+		size = 'big',
+		border = 'solid',
+		dividerHeight = 'mid',
+		heightIn = '3',
+		heightOut = '2',
+		injClass = '',
+		children
+	}: ButtonGroupProps = $props();
+
+	// fill + state 组合样式映射(与 Button 组件保持一致)
+	// fill + state combination style mapping (consistent with Button component)
+
+	// base 模式:实心填充,跟随 state
+	const stateBaseObj: Record<string, string> = {
+		theme: 'bg-primary dark:bg-dark',
+		success: 'bg-success',
+		warning: 'bg-warning',
+		error: 'bg-error',
+		info: 'bg-info'
+	};
+
+	// line 模式:无色边框
+	const fillLineClass = 'border border-text-primary dark:border-text-dark !text-text-primary dark:!text-text-dark';
+
+	// lineLight 模式:浅色无色边框
+	const fillLineLightClass = 'border border-text-primary/20 dark:border-text-dark/30 !text-text-primary dark:!text-text-dark';
+
+	// lineState 模式:状态色边框
+	const stateLineStateObj: Record<string, string> = {
+		theme: 'border border-primary dark:border-dark !text-primary dark:!text-dark',
+		success: 'border border-success !text-success',
+		warning: 'border border-warning !text-warning',
+		error: 'border border-error !text-error',
+		info: 'border border-info !text-info'
+	};
+
+	// text 模式:无色纯文本
+	const fillTextClass = '!text-text-primary dark:!text-text-dark';
+
+	// textState 模式:状态色纯文本
+	const stateTextStateObj: Record<string, string> = {
+		theme: '!text-primary dark:!text-dark',
+		success: '!text-success',
+		warning: '!text-warning',
+		error: '!text-error',
+		info: '!text-info'
+	};
+
+	// colorLight 模式:浅色填充,默认浅灰,state 指定时跟随 state
+	const stateColorLightObj: Record<string, string> = {
+		default: '!bg-black/10 dark:!bg-white/10 !text-black dark:!text-white',
+		theme: '!bg-primary/10 dark:!bg-dark/10 !text-primary dark:!text-dark',
+		success: '!bg-success/10 !text-success',
+		warning: '!bg-warning/10 !text-warning',
+		error: '!bg-error/10 !text-error',
+		info: '!bg-info/10 !text-info'
+	};
+
+	// 计算 fill + state 组合样式
+	const fillStateClass = $derived.by(() => {
+		switch (fill) {
+			case 'base':
+				return stateBaseObj[state || 'theme'];
+			case 'line':
+				return fillLineClass;
+			case 'lineLight':
+				return fillLineLightClass;
+			case 'lineState':
+				return stateLineStateObj[state || 'theme'];
+			case 'text':
+				return fillTextClass;
+			case 'textState':
+				return stateTextStateObj[state || 'theme'];
+			case 'colorLight':
+				// colorLight 模式:未指定 state 时使用灰色,指定 state 时跟随 state
+				return state ? stateColorLightObj[state] : stateColorLightObj.default;
+			default:
+				return stateBaseObj[state || 'theme'];
+		}
+	});
+
+	// 分割线高度映射 full(100%) / mid(60%) / short(30%)
+	// Divider height mapping
+	const dividerHeightObj: Record<string, string> = {
+		full: 'h-full',
+		mid: 'h-[60%]',
+		short: 'h-[30%]'
+	};
+
+	// 分割线颜色(跟随文字颜色)
+	// Divider color (follows text color)
+	const dividerColorClass = $derived.by(() => {
+		if (fill === 'base') {
+			// base 模式下文字是白色/黑色,分割线用半透明白色/黑色
+			return 'bg-white/30 dark:bg-black/30';
+		}
+		// 其他模式分割线颜色跟随状态色
+		const stateColorMap: Record<string, string> = {
+			theme: 'bg-primary/30 dark:bg-dark/30',
+			success: 'bg-success/30',
+			warning: 'bg-warning/30',
+			error: 'bg-error/30',
+			info: 'bg-info/30'
+		};
+		// line/lineLight/text/colorLight(未指定 state)模式使用无色分割线
+		if (fill === 'line' || fill === 'lineLight' || fill === 'text' || (fill === 'colorLight' && !state)) {
+			return 'bg-text-primary/20 dark:bg-text-dark/30';
+		}
+		return stateColorMap[state ?? 'theme'];
+	});
+
+	// 尺寸样式
+	const sizeObj: Record<string, string> = { full: 'w-full', big: 'w-full', md: 'w-[50vw]', sm: 'w-[25vw]', auto: 'w-auto' };
+
+	// 边框风格样式
+	const borderObj: Record<string, string> = { solid: 'border-solid', dashed: 'border-dashed', dotted: 'border-dotted' };
+
+	// 按钮外部高度样式
+	const heightOutObj: Record<string, string> = { '0': 'py-0', '1': 'py-1', '2': 'py-2', '3': 'py-3', '4': 'py-4' };
+
+	// 按钮高度样式
+	const heightInObj: Record<string, string> = { '0': 'py-0', '1': 'py-1', '2': 'py-2', '3': 'py-3', '4': 'py-4' };
+
+	// 是否块级元素
+	let block = $derived(size === 'full' || size === 'big');
+
+	// 文字颜色(仅 base 模式需要反色文字)
+	const textColor = $derived.by(() => {
+		if (fill === 'base') {
+			return state === 'theme' || !state ? 'text-text-on-primary dark:text-text-on-dark' : 'text-text-on-primary';
+		}
+		return '';
+	});
+
+	// 是否使用 items 数组模式
+	const useItems = $derived(items && items.length > 0);
+</script>
+
+<div class="{heightOutObj[heightOut] || heightOutObj['2']} {size === 'big' ? 'px-4' : 'px-0'} {block ? 'block' : 'inline'}">
+	<div
+		class="inline-flex items-center justify-center overflow-hidden {sizeObj[size] || sizeObj.big} {fillStateClass} {textColor} {borderObj[border] || borderObj.solid} {radius ? radiusObj[radius] : 'rounded-(--radius-form)'} {injClass}"
+	>
+		{#if useItems}
+			<!-- items 数组模式 -->
+			{#each items as item, index (index)}
+				<button
+					class="flex flex-1 items-center justify-center gap-1 {heightInObj[heightIn] || heightInObj['3']} {item.disabled ? 'cursor-not-allowed opacity-50' : 'active:opacity-80'}"
+					disabled={item.disabled}
+					onclick={item.onclick}
+				>
+					{#if item.icon && item.icon.name && item.iconPosition !== 'right'}
+						<Icon {...item.icon} />
+					{/if}
+					{#if item.text}
+						{item.text}
+					{/if}
+					{#if item.icon && item.icon.name && item.iconPosition === 'right'}
+						<Icon {...item.icon} />
+					{/if}
+				</button>
+				<!-- 分割线(最后一项后不显示) -->
+				{#if index < items.length - 1}
+					<div class="flex items-center self-stretch">
+						<div class="w-px {dividerHeightObj[dividerHeight] || dividerHeightObj.mid} {dividerColorClass}"></div>
+					</div>
+				{/if}
+			{/each}
+		{:else if children}
+			<!-- children 自定义模式 -->
+			{@render children()}
+		{/if}
+	</div>
+</div>

+ 319 - 220
packages/stdf/src/lib/components/calendar/Calendar.svelte

@@ -2,6 +2,7 @@
 	import { getContext } from 'svelte';
 	import Popup from '../popup/Popup.svelte';
 	import Button from '../button/Button.svelte';
+	import Card from '../card/Card.svelte';
 	import {
 		getNowBeforeOrAfterMonth,
 		getCalendarData,
@@ -10,7 +11,8 @@
 		getCurrentWeek,
 		getCurrentMonth,
 		getCurrentQuarter,
-		getDaysRangeWithToday
+		getDaysRangeWithToday,
+		radiusObj
 	} from '../utils/index.js';
 	import { zh_CN, type LangProps } from '../../lang/index.js';
 	import type { CalendarProps } from '../../types/index.js';
@@ -25,6 +27,7 @@
 		startMonth = getNowBeforeOrAfterMonth(-6),
 		endMonth = getNowBeforeOrAfterMonth(6),
 		initMonth = getNowBeforeOrAfterMonth(0),
+		initSelectedDates = [],
 		mode = 'single',
 		startSunday = false,
 		weekendRed = false,
@@ -34,7 +37,7 @@
 		height = 50,
 		infoDates = [],
 		disabledDates = [],
-		radius = 'xl',
+		radius = '',
 		showSelectedDay = true,
 		confirmText = calendarLang.confirmText,
 		selectedText = calendarLang.selectedText,
@@ -46,14 +49,15 @@
 		outFormat = 'YYYYMMDD',
 		popup = {},
 		button = {},
+		card = {},
 		clear = true,
 		onconfirm,
 		onclose
 	}: CalendarProps = $props();
 
-	// 圆角风格样式
-	// Rounded style style
-	const radiusClass = { sm: 'rounded-sm', xl: 'rounded-xl', '2xl': 'rounded-2xl', none: 'rounded-none' };
+	// 是否使用弹出层,当 popup 为 null 时不使用
+	// Whether to use popup, when popup is null, do not use
+	const usePopup = $derived(popup !== null);
 
 	// 月份水印文字大小样式
 	// Month watermark text size style
@@ -85,7 +89,7 @@
 
 	// 定义周数据文字
 	// Define the week data text
-	const weekTexts = startSunday ? calendarLang.weekSundayStartTextList : calendarLang.weekTextList;
+	const weekTexts = $derived(startSunday ? calendarLang.weekSundayStartTextList : calendarLang.weekTextList);
 
 	// 传入 startSunday 和位于周的索引,返回是否是周末
 	// Pass in startSunday and the index of the week, and return whether it is a weekend
@@ -99,61 +103,70 @@
 
 	// 根据 startMonth 和 endMonth 将月份数据组成 monthList
 	// According to startMonth and endMonth, the month data is assembled into monthList
-	const monthList = getMonthListRange(startMonth, endMonth);
+	const monthList = $derived(getMonthListRange(startMonth, endMonth));
 
 	// 如果 initMonth 在 startMonth 和 endMonth 之间 initMonthIndex 为 initMonth 在 monthList 中的索引,如果在 startMonth 之前,initMonthIndex 为 0,如果在 endMonth 之后,initMonthIndex 为 monthList.length - 1
 	// If initMonth is between startMonth and endMonth, initMonthIndex is the index of initMonth in monthList. If it is before startMonth, initMonthIndex is 0. If it is after endMonth, initMonthIndex is monthList.length - 1
-	const initMonthIndex = monthList.findIndex((item) => item === initMonth);
+	const initMonthIndex = $derived(monthList.findIndex((item) => item === initMonth));
 
 	// 循环 monthList,根据每个月份的数据,生成全部的日历数据,其中 month 为当前月,year 为年,data 为日期数组
 	// Loop monthList, generate all calendar data according to the data of each month, where month is the current month, year is the year, and data is the date array
-	const allMonthData: {
-		year: string;
-		month: string;
-		days: { text: string; info?: string; disabled?: boolean; start?: boolean; end?: boolean }[];
-	}[] = monthList.map((monthStr) => ({
-		year: monthStr.slice(0, 4),
-		month: monthStr.slice(4),
-		days: getCalendarData(monthStr, startSunday).map((item) => ({
-			text: item,
-			info: '',
-			disabled: false,
-			start: false,
-			end: false
-		}))
-	}));
+	const allMonthData = $derived.by(() => {
+		const data: {
+			year: string;
+			month: string;
+			days: { text: string; info?: string; disabled?: boolean; start?: boolean; end?: boolean }[];
+		}[] = monthList.map((monthStr) => ({
+			year: monthStr.slice(0, 4),
+			month: monthStr.slice(4),
+			days: getCalendarData(monthStr, startSunday).map((item) => ({
+				text: item,
+				info: '',
+				disabled: false,
+				start: false,
+				end: false
+			}))
+		}));
 
-	// 如果 infoDates 不为空,循环 allMonthData,循环 data,循环 infoDates,如果 infoDates 中的日期和 data 中的日期相同,将 infoDates 中的 info 赋值给 data 中的 info
-	// If infoDates is not empty, loop allMonthData, loop data, loop infoDates, if the date in infoDates is the same as the date in data, assign the info in infoDates to the info in data
-	if (infoDates.length > 0) {
-		allMonthData.forEach((monthData) => {
-			monthData.days.forEach((item) => {
-				infoDates.forEach((info) => {
-					if (info.text === monthData.year + monthData.month + item.text) {
-						item.info = info.info;
-					}
+		// 如果 infoDates 不为空,循环 allMonthData,循环 data,循环 infoDates,如果 infoDates 中的日期和 data 中的日期相同,将 infoDates 中的 info 赋值给 data 中的 info
+		// If infoDates is not empty, loop allMonthData, loop data, loop infoDates, if the date in infoDates is the same as the date in data, assign the info in infoDates to the info in data
+		if (infoDates.length > 0) {
+			data.forEach((monthData) => {
+				monthData.days.forEach((item) => {
+					infoDates.forEach((info) => {
+						if (info.text === monthData.year + monthData.month + item.text) {
+							item.info = info.info;
+						}
+					});
 				});
 			});
-		});
-	}
+		}
 
-	// 如果 disabledDates 不为空,循环 allMonthData,循环 data,循环 disabledDates,如果 disabledDates 中的日期和 data 中的日期相同,将 disabledDates 中的 disabled 赋值给 data 中的 disabled
-	// If disabledDates is not empty, loop allMonthData, loop data, loop disabledDates, if the date in disabledDates is the same as the date in data, assign the disabled in disabledDates to the disabled in data
-	if (disabledDates.length > 0) {
-		allMonthData.forEach((monthData) => {
-			monthData.days.forEach((item) => {
-				disabledDates.forEach((disabled) => {
-					if (disabled === monthData.year + monthData.month + item.text) {
-						item.disabled = true;
-					}
+		// 如果 disabledDates 不为空,循环 allMonthData,循环 data,循环 disabledDates,如果 disabledDates 中的日期和 data 中的日期相同,将 disabledDates 中的 disabled 赋值给 data 中的 disabled
+		// If disabledDates is not empty, loop allMonthData, loop data, loop disabledDates, if the date in disabledDates is the same as the date in data, assign the disabled in disabledDates to the disabled in data
+		if (disabledDates.length > 0) {
+			data.forEach((monthData) => {
+				monthData.days.forEach((item) => {
+					disabledDates.forEach((disabled) => {
+						if (disabled === monthData.year + monthData.month + item.text) {
+							item.disabled = true;
+						}
+					});
 				});
 			});
-		});
-	}
+		}
+
+		return data;
+	});
 
 	// 选中的日期
 	// Selected date
 	let selectedDate: string[] = $state([]);
+	$effect(() => {
+		if (initSelectedDates.length > 0) {
+			selectedDate = [...initSelectedDates].sort((a, b) => Number(a) - Number(b));
+		}
+	});
 
 	// 范围选择时,点击的开始与结束
 	// When range selection is clicked, the beginning and end are clicked
@@ -161,7 +174,7 @@
 
 	// selectedDate 字符串
 	// selectedDate string
-	let selectedDateStr = $state('');
+	let selectedDateStr = $derived(selectedDate.join(','));
 
 	// 点击日期事件
 	// Click date event
@@ -185,6 +198,12 @@
 				rangeArr.sort((a, b) => Number(a) - Number(b));
 				selectedDate = getDateRange(rangeArr[0], rangeArr[1], disabledDates);
 				rangeArr = [];
+				// 不使用 Popup 时,范围选择完成后立即触发 onconfirm
+				// When not using Popup, trigger onconfirm immediately after range selection is complete
+				if (!usePopup) {
+					const formattedDates = selectedDate.map((item) => getDateStrFunc(outFormat, item));
+					onconfirm?.(formattedDates);
+				}
 			} else {
 				selectedDate = rangeArr;
 			}
@@ -195,8 +214,21 @@
 			} else {
 				selectedDate.splice(index, 1);
 			}
+			// 不使用 Popup 时,多选模式每次点击都触发 onconfirm
+			// When not using Popup, trigger onconfirm on each click in multiple mode
+			if (!usePopup) {
+				const sortedDates = [...selectedDate].sort((a, b) => Number(a) - Number(b));
+				const formattedDates = sortedDates.map((item) => getDateStrFunc(outFormat, item));
+				onconfirm?.(formattedDates);
+			}
 		} else {
 			selectedDate = [`${year}${month}${days.text}`];
+			// 不使用 Popup 时,单选模式点击即触发 onconfirm
+			// When not using Popup, trigger onconfirm immediately on click in single mode
+			if (!usePopup) {
+				const formattedDates = selectedDate.map((item) => getDateStrFunc(outFormat, item));
+				onconfirm?.(formattedDates);
+			}
 		}
 		// 对 selectedDate 按照日期排序
 		// Sort selectedDate by date
@@ -293,189 +325,256 @@
 	});
 </script>
 
-<Popup
-	bind:visible
-	size={0}
-	maskClosable
-	transitionDistance={(window.innerHeight * height) / 100 + 40 + 64 + (quickSelects.length > 0 && mode === 'range' ? 36 : 0)}
-	{...popup}
->
-	<div>
-		<div class="sticky left-0 top-0 z-10 w-full bg-gray-50 dark:bg-gray-800">
-			{#if quickSelects.length > 0 && mode === 'range'}
-				<div class="calendar-container flex flex-nowrap gap-4 overflow-x-auto px-4 pb-1 pt-2">
-					{#each quickSelects as item, index (index)}
+{#snippet calendarContent()}
+	<div class="sticky left-0 top-0 z-10 w-full {usePopup ? 'bg-gray-50 dark:bg-gray-800' : ''}">
+		{#if quickSelects.length > 0 && mode === 'range'}
+			<div class="calendar-container flex flex-nowrap gap-4 overflow-x-auto px-4 pb-1 pt-2">
+				{#each quickSelects as item, index (index)}
+					<button
+						class="bg-bg-surface dark:bg-bg-surface-dark flex-none rounded-sm px-2 py-1 text-xs shadow-sm dark:shadow-white/10"
+						class:!bg-primary={isQuickSelect && quickSelectItem === item}
+						class:!text-text-on-primary={isQuickSelect && quickSelectItem === item}
+						class:dark:!bg-dark={isQuickSelect && quickSelectItem === item}
+						class:dark:!text-text-on-dark={isQuickSelect && quickSelectItem === item}
+						onclick={() => quickSelectFunc(item)}
+					>
+						{#if item === 'week'}
+							{calendarLang.currentWeekText}
+						{:else if item === 'month'}
+							{calendarLang.currentMonthText}
+						{:else if item === 'quarter'}
+							{calendarLang.currentQuarterText}
+						{:else if Number.isInteger(item) && item < 0}
+							{calendarLang.beforeText + ' ' + Math.abs(item) + ' ' + calendarLang.dayText}
+						{:else if Number.isInteger(item) && item > 0}
+							{calendarLang.afterText + ' ' + Math.abs(item) + ' ' + calendarLang.dayText}
+						{:else}
+							<!-- else content here -->
+						{/if}
+					</button>
+				{/each}
+			</div>
+		{/if}
+		<div class="flex h-10 items-center justify-around gap-1 px-6 text-center leading-10">
+			{#each weekTexts as item, index (index)}
+				<div class="flex-1 font-bold{weekendRed && isWeekendFunc(startSunday, index) ? ' text-error' : ''}">
+					{item}
+				</div>
+			{/each}
+		</div>
+	</div>
+	<div
+		class="calendar-container flex flex-col gap-4 overflow-y-auto px-4 py-2 {usePopup ? 'bg-gray-50 dark:bg-gray-800' : ''} {useAnimation
+			? 'scroll-smooth'
+			: 'scroll-auto'}"
+		style="height:{(window.innerHeight * height) / 100}px"
+		bind:this={scrollElement}
+	>
+		{#snippet monthContentInner(monthItem: {
+			year: string;
+			month: string;
+			days: { text: string; info?: string; disabled?: boolean; start?: boolean; end?: boolean }[];
+		})}
+			<div class="relative">
+				<div class="text-text-primary/30 dark:text-text-dark/30 px-4 pt-4 text-xl">
+					<span class="text-text-primary dark:text-text-dark mr-2 font-bold">
+						{calendarLang.monthTextList[Number(monthItem.month) - 1]}
+					</span>
+					{monthItem.year}
+				</div>
+				<div class="grid grid-cols-7 gap-y-1 p-2 text-center">
+					{#each monthItem.days as day, dayIndex (dayIndex)}
 						<button
-							class="flex-none rounded-sm bg-white px-2 py-1 text-xs shadow-sm dark:bg-black dark:shadow-white/10"
-							class:!bg-primary={isQuickSelect && quickSelectItem === item}
-							class:text-white={isQuickSelect && quickSelectItem === item}
-							class:dark:!bg-dark={isQuickSelect && quickSelectItem === item}
-							class:dark:text-black={isQuickSelect && quickSelectItem === item}
-							onclick={() => quickSelectFunc(item)}
+							class="p-px{day.text ? ' bg-primary/10 dark:bg-dark/20' : ''}"
+							onclick={() => {
+								if (!day.disabled) clickDayFunc(monthItem.year, monthItem.month, day);
+							}}
+							style={!radius
+								? mode === 'range'
+									? selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`)
+										? (selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start
+												? 'border-top-left-radius:var(--radius-form);border-bottom-left-radius:var(--radius-form);'
+												: '') +
+											(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end
+												? 'border-top-right-radius:var(--radius-form);border-bottom-right-radius:var(--radius-form);'
+												: '')
+										: ''
+									: 'border-radius:var(--radius-form);'
+								: ''}
+							class:!bg-transparent={!selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`)}
+							class:!rounded-l-none={radius === 'none' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-none={radius === 'none' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-none={radius === 'none' && mode !== 'range'}
+							class:!rounded-l-xs={radius === 'xs' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-xs={radius === 'xs' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-xs={radius === 'xs' && mode !== 'range'}
+							class:!rounded-l-sm={radius === 'sm' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-sm={radius === 'sm' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-sm={radius === 'sm' && mode !== 'range'}
+							class:!rounded-l-md={radius === 'md' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-md={radius === 'md' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-md={radius === 'md' && mode !== 'range'}
+							class:!rounded-l-lg={radius === 'lg' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-lg={radius === 'lg' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-lg={radius === 'lg' && mode !== 'range'}
+							class:!rounded-l-xl={radius === 'xl' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-xl={radius === 'xl' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-xl={radius === 'xl' && mode !== 'range'}
+							class:!rounded-l-2xl={radius === '2xl' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-2xl={radius === '2xl' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-2xl={radius === '2xl' && mode !== 'range'}
+							class:!rounded-l-full={radius === 'full' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 || day.start)}
+							class:!rounded-r-full={radius === 'full' &&
+								mode === 'range' &&
+								(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
+							class:!rounded-full={radius === 'full' && mode !== 'range'}
 						>
-							{#if item === 'week'}
-								{calendarLang.currentWeekText}
-							{:else if item === 'month'}
-								{calendarLang.currentMonthText}
-							{:else if item === 'quarter'}
-								{calendarLang.currentQuarterText}
-							{:else if Number.isInteger(item) && item < 0}
-								{calendarLang.beforeText + ' ' + Math.abs(item) + ' ' + calendarLang.dayText}
-							{:else if Number.isInteger(item) && item > 0}
-								{calendarLang.afterText + ' ' + Math.abs(item) + ' ' + calendarLang.dayText}
-							{:else}
-								<!-- else content here -->
-							{/if}
+							<div
+								class="{radius
+									? radiusObj[radius]
+									: 'rounded-(--radius-form)'} relative flex h-full w-full flex-col justify-center {day.info ? 'py-1' : 'py-2'}"
+								class:bg-primary={(day.text &&
+									mode !== 'range' &&
+									selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`)) ||
+									(day.text &&
+										selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`) &&
+										mode === 'range' &&
+										(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 ||
+											selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8))}
+								class:dark:bg-dark={(day.text &&
+									mode !== 'range' &&
+									selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`)) ||
+									(day.text &&
+										selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`) &&
+										mode === 'range' &&
+										(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 ||
+											selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8))}
+								class:!text-text-on-primary={(day.text &&
+									mode !== 'range' &&
+									selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`)) ||
+									(day.text &&
+										selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`) &&
+										mode === 'range' &&
+										(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 ||
+											selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8))}
+								class:dark:!text-text-on-dark={(day.text &&
+									mode !== 'range' &&
+									selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`)) ||
+									(day.text &&
+										selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`) &&
+										mode === 'range' &&
+										(selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === 0 ||
+											selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) === selectedDateStr.length - 8))}
+								class:text-primary={mode === 'range' &&
+									selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`) &&
+									selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) !== 0 &&
+									selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) !== selectedDateStr.length - 8}
+								class:dark:text-dark={mode === 'range' &&
+									selectedDateStr.includes(`${monthItem.year}${monthItem.month}${day.text}`) &&
+									selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) !== 0 &&
+									selectedDateStr.indexOf(`${monthItem.year}${monthItem.month}${day.text}`) !== selectedDateStr.length - 8}
+								class:border={monthItem.year + monthItem.month + day.text === todayStr && highlightToday}
+								class:border-primary={monthItem.year + monthItem.month + day.text === todayStr && highlightToday}
+								class:dark:border-dark={monthItem.year + monthItem.month + day.text === todayStr && highlightToday}
+							>
+								<div class="font-bold" class:text-error={weekendRed && day.text && isWeekendFunc(startSunday, dayIndex % 7)}>
+									{day.text ? Number(day.text) : ''}
+								</div>
+								<div class="text-xs opacity-50">{day.info || ''}</div>
+								{#if day.text && day.disabled}
+									<div
+										class="text-text-primary/60 dark:text-text-dark absolute left-1/2 top-1/2 h-4/5 w-4/5 -translate-x-1/2 -translate-y-1/2 opacity-40"
+									>
+										<svg viewBox="0 0 1024 1024" version="1.1" width="100%" height="100%" style="fill: currentColor;">
+											<path
+												d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333zM170.666667 512a341.333333 341.333333 0 0 0 550.016 270.08L241.92 303.36A339.84 339.84 0 0 0 170.666667 512z m341.333333-341.333333a339.882667 339.882667 0 0 0-209.877333 72.106666L781.226667 721.92A341.333333 341.333333 0 0 0 512 170.666667z"
+											/>
+										</svg>
+									</div>
+								{/if}
+							</div>
 						</button>
 					{/each}
 				</div>
-			{/if}
-			<div class="flex h-10 items-center justify-around gap-1 px-6 text-center leading-10">
-				{#each weekTexts as item, index (index)}
-					<div class="flex-1 font-bold{weekendRed && isWeekendFunc(startSunday, index) ? ' text-error' : ''}">
-						{item}
+				{#if monthMark}
+					<div
+						class="pointer-events-none absolute w-full {monthMarkSizeClass[monthMarkSize] ||
+							'text-7xl'} text-text-primary/5 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center font-bold {monthCard
+							? 'dark:text-text-dark/10'
+							: 'dark:text-text-dark/5'}"
+					>
+						{calendarLang.monthTextList[Number(monthItem.month) - 1]}
 					</div>
-				{/each}
+				{/if}
+			</div>
+		{/snippet}
+		{#each allMonthData as item, index (index)}
+			{#if monthCard}
+				<Card mx="0" my="0" px="0" py="2" {...card} overflow={false}>
+					{@render monthContentInner(item)}
+				</Card>
+			{:else}
+				{@render monthContentInner(item)}
+			{/if}
+		{/each}
+	</div>
+{/snippet}
+
+{#if usePopup}
+	<Popup
+		bind:visible
+		size={0}
+		maskClosable
+		transitionDistance={(window.innerHeight * height) / 100 + 40 + 64 + (quickSelects.length > 0 && mode === 'range' ? 36 : 0)}
+		{...popup}
+	>
+		<div>
+			{@render calendarContent()}
+			<div class="bg-bg-surface dark:bg-bg-surface-dark sticky bottom-0 left-0 z-10 w-full">
+				<Button {...button} onclick={confirmFunc}>
+					{confirmText}
+					{showSelectedDay && mode !== 'single' && selectedDateStr.split(',').length > 0
+						? `(${selectedText} ${selectedDate.length} ${dayText})`
+						: ''}
+				</Button>
 			</div>
 		</div>
-		<div
-			class="calendar-container flex flex-col gap-4 overflow-y-auto bg-gray-50 px-4 py-2 dark:bg-gray-800 {useAnimation
-				? 'scroll-smooth'
-				: 'scroll-auto'}"
-			style="height:{(window.innerHeight * height) / 100}px"
-			bind:this={scrollElement}
-		>
-			{#each allMonthData as item, index (index)}
-				<div
-					class="{monthCard ? `bg-white shadow-md dark:bg-black dark:shadow-white/5 ${radiusClass[radius] || 'rounded-xl'}` : ''} relative"
-				>
-					<div class="px-4 pt-4 text-xl text-black/30 dark:text-white/30">
-						<span class="mr-2 font-bold text-black dark:text-white">
-							{calendarLang.monthTextList[Number(item.month) - 1]}
-						</span>
-						{item.year}
-					</div>
-					<div class="grid grid-cols-7 gap-y-1 p-2 text-center">
-						{#each item.days as day, index (index)}
-							<button
-								class="p-px{day.text ? ' bg-primary/10 dark:bg-dark/20' : ''}"
-								onclick={() => {
-									if (!day.disabled) clickDayFunc(item.year, item.month, day);
-								}}
-								class:!bg-transparent={!selectedDateStr.includes(`${item.year}${item.month}${day.text}`)}
-								class:!rounded-l-xl={radius === 'xl' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 || day.start)}
-								class:!rounded-r-xl={radius === 'xl' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
-								class:!rounded-xl={radius === 'xl' && mode !== 'range'}
-								class:!rounded-l-2xl={radius === '2xl' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 || day.start)}
-								class:!rounded-r-2xl={radius === '2xl' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
-								class:!rounded-2xl={radius === '2xl' && mode !== 'range'}
-								class:!rounded-l-none={radius === 'none' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 || day.start)}
-								class:!rounded-r-none={radius === 'none' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
-								class:!rounded-none={radius === 'none' && mode !== 'range'}
-								class:!rounded-l-sm={radius === 'sm' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 || day.start)}
-								class:!rounded-r-sm={radius === 'sm' &&
-									mode === 'range' &&
-									(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8 || day.end)}
-								class:!rounded-sm={radius === 'sm' && mode !== 'range'}
-							>
-								<div
-									class="{radiusClass[radius] || 'rounded-xl'} relative flex h-full w-full flex-col justify-center {day.info
-										? 'py-1'
-										: 'py-2'}"
-									class:bg-primary={(day.text && mode !== 'range' && selectedDateStr.includes(`${item.year}${item.month}${day.text}`)) ||
-										(day.text &&
-											selectedDateStr.includes(`${item.year}${item.month}${day.text}`) &&
-											mode === 'range' &&
-											(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 ||
-												selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8))}
-									class:dark:bg-dark={(day.text && mode !== 'range' && selectedDateStr.includes(`${item.year}${item.month}${day.text}`)) ||
-										(day.text &&
-											selectedDateStr.includes(`${item.year}${item.month}${day.text}`) &&
-											mode === 'range' &&
-											(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 ||
-												selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8))}
-									class:text-white={(day.text && mode !== 'range' && selectedDateStr.includes(`${item.year}${item.month}${day.text}`)) ||
-										(day.text &&
-											selectedDateStr.includes(`${item.year}${item.month}${day.text}`) &&
-											mode === 'range' &&
-											(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 ||
-												selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8))}
-									class:dark:text-black={(day.text &&
-										mode !== 'range' &&
-										selectedDateStr.includes(`${item.year}${item.month}${day.text}`)) ||
-										(day.text &&
-											selectedDateStr.includes(`${item.year}${item.month}${day.text}`) &&
-											mode === 'range' &&
-											(selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === 0 ||
-												selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) === selectedDateStr.length - 8))}
-									class:text-primary={mode === 'range' &&
-										selectedDateStr.includes(`${item.year}${item.month}${day.text}`) &&
-										selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) !== 0 &&
-										selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) !== selectedDateStr.length - 8}
-									class:dark:text-dark={mode === 'range' &&
-										selectedDateStr.includes(`${item.year}${item.month}${day.text}`) &&
-										selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) !== 0 &&
-										selectedDateStr.indexOf(`${item.year}${item.month}${day.text}`) !== selectedDateStr.length - 8}
-									class:border={item.year + item.month + day.text === todayStr && highlightToday}
-									class:border-primary={item.year + item.month + day.text === todayStr && highlightToday}
-									class:dark:border-dark={item.year + item.month + day.text === todayStr && highlightToday}
-								>
-									<div class="font-bold">
-										{day.text ? Number(day.text) : ''}
-									</div>
-									<div class="text-xs opacity-50">{day.info || ''}</div>
-									{#if day.text && day.disabled}
-										<div
-											class="absolute left-1/2 top-1/2 h-4/5 w-4/5 -translate-x-1/2 -translate-y-1/2 text-black/60 opacity-40 dark:text-white"
-										>
-											<svg viewBox="0 0 1024 1024" version="1.1" width="100%" height="100%" style="fill: currentColor;">
-												<path
-													d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333zM170.666667 512a341.333333 341.333333 0 0 0 550.016 270.08L241.92 303.36A339.84 339.84 0 0 0 170.666667 512z m341.333333-341.333333a339.882667 339.882667 0 0 0-209.877333 72.106666L781.226667 721.92A341.333333 341.333333 0 0 0 512 170.666667z"
-												/>
-											</svg>
-										</div>
-									{/if}
-								</div>
-							</button>
-						{/each}
-					</div>
-					{#if monthMark}
-						<div
-							class="pointer-events-none absolute w-full {monthMarkSizeClass[monthMarkSize] ||
-								'text-7xl'} left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-center font-bold text-black/5 {monthCard
-								? 'dark:text-white/10'
-								: 'dark:text-white/5'}"
-						>
-							{calendarLang.monthTextList[Number(item.month) - 1]}
-						</div>
-					{/if}
-				</div>
-			{/each}
-		</div>
-		<div class="sticky bottom-0 left-0 z-10 w-full bg-gray-50 dark:bg-gray-800">
-			<Button {...button} onclick={confirmFunc}>
-				{confirmText}
-				{showSelectedDay && mode !== 'single' && selectedDateStr.split(',').length > 0
-					? `(${selectedText} ${selectedDate.length} ${dayText})`
-					: ''}
-			</Button>
-		</div>
+	</Popup>
+{:else}
+	<div>
+		{@render calendarContent()}
 	</div>
-</Popup>
+{/if}
 
 <style>
 	.calendar-container::-webkit-scrollbar {

+ 176 - 0
packages/stdf/src/lib/components/card/Card.svelte

@@ -0,0 +1,176 @@
+<script lang="ts">
+	import type { CardProps } from '../../types/index.js';
+	import { radiusObj, bgObj } from '../utils/index.js';
+
+	let {
+		bg = 'surface',
+		radius = '',
+		shadow = 'sm',
+		border = 'none',
+		borderWidth = '1',
+		mx = '2',
+		my = '2',
+		p = '4',
+		px,
+		py,
+		overflow = true,
+		headerLine = true,
+		footerLine = true,
+		injClass = '',
+		headerClass = '',
+		bodyClass = '',
+		footerClass = '',
+		header,
+		children,
+		footer,
+		onclick
+	}: CardProps = $props();
+
+	// 阴影样式
+	// Shadow style
+	const shadowObj = {
+		none: 'shadow-none',
+		xs: 'shadow-xs dark:shadow-white/5',
+		sm: 'shadow-sm dark:shadow-white/5',
+		md: 'shadow-md dark:shadow-white/10',
+		lg: 'shadow-lg dark:shadow-white/10',
+		xl: 'shadow-xl dark:shadow-white/10',
+		'2xl': 'shadow-2xl dark:shadow-white/20'
+	};
+
+	// 边框风格样式
+	// Border style
+	const borderObj = {
+		none: '',
+		solid: 'border-solid border-black/10 dark:border-white/10',
+		dashed: 'border-dashed border-black/10 dark:border-white/10',
+		dotted: 'border-dotted border-black/10 dark:border-white/10'
+	};
+
+	// 边框粗细样式
+	// Border width style
+	const borderWidthObj = {
+		'0': 'border-0',
+		'1': 'border',
+		'2': 'border-2',
+		'4': 'border-4'
+	};
+
+	// 外边距样式
+	// Margin style
+	const mxObj = {
+		'0': 'mx-0',
+		'1': 'mx-1',
+		'2': 'mx-2',
+		'3': 'mx-3',
+		'4': 'mx-4',
+		'6': 'mx-6',
+		'8': 'mx-8'
+	};
+
+	const myObj = {
+		'0': 'my-0',
+		'1': 'my-1',
+		'2': 'my-2',
+		'3': 'my-3',
+		'4': 'my-4',
+		'6': 'my-6',
+		'8': 'my-8'
+	};
+
+	// 内边距样式
+	// Padding style
+	const pObj = {
+		'0': 'p-0',
+		'1': 'p-1',
+		'2': 'p-2',
+		'3': 'p-3',
+		'4': 'p-4',
+		'6': 'p-6',
+		'8': 'p-8'
+	};
+
+	const pxObj = {
+		'0': 'px-0',
+		'1': 'px-1',
+		'2': 'px-2',
+		'3': 'px-3',
+		'4': 'px-4',
+		'6': 'px-6',
+		'8': 'px-8'
+	};
+
+	const pyObj = {
+		'0': 'py-0',
+		'1': 'py-1',
+		'2': 'py-2',
+		'3': 'py-3',
+		'4': 'py-4',
+		'6': 'py-6',
+		'8': 'py-8'
+	};
+
+	// 计算 padding 类名
+	// Calculate padding class name
+	const paddingClass = $derived(px || py ? `${px ? pxObj[px] : ''} ${py ? pyObj[py] : ''}` : pObj[p] || pObj['4']);
+
+	// 计算边框类名
+	// Calculate border class name
+	const borderClass = $derived(border !== 'none' ? `${borderObj[border]} ${borderWidthObj[borderWidth]}` : '');
+
+	// 容器样式
+	// Container style
+	const containerClass = $derived(
+		`${overflow ? 'overflow-hidden' : ''} ${bg in bgObj ? bgObj[bg] : bgObj['surface']} ${radius ? radiusObj[radius] : 'rounded-(--radius-box)'} ${shadowObj[shadow] || shadowObj['sm']} ${mxObj[mx] || mxObj['2']} ${myObj[my] || myObj['2']} ${borderClass} ${injClass}`
+	);
+</script>
+
+{#if onclick}
+	<button class="block text-left {containerClass}" {onclick}>
+		{#if header}
+			<div class="px-4 py-3 {headerClass}">
+				{@render header()}
+			</div>
+			{#if headerLine && children}
+				<div class="border-t border-black/5 dark:border-white/5"></div>
+			{/if}
+		{/if}
+		{#if children}
+			<div class="{paddingClass} {bodyClass}">
+				{@render children()}
+			</div>
+		{/if}
+		{#if footer}
+			{#if footerLine && children}
+				<div class="border-t border-black/5 dark:border-white/5"></div>
+			{/if}
+			<div class="px-4 py-3 {footerClass}">
+				{@render footer()}
+			</div>
+		{/if}
+	</button>
+{:else}
+	<div class="{containerClass}">
+		{#if header}
+			<div class="px-4 py-3 {headerClass}">
+				{@render header()}
+			</div>
+			{#if headerLine && children}
+				<div class="border-t border-black/5 dark:border-white/5"></div>
+			{/if}
+		{/if}
+		{#if children}
+			<div class="{paddingClass} {bodyClass}">
+				{@render children()}
+			</div>
+		{/if}
+		{#if footer}
+			{#if footerLine && children}
+				<div class="border-t border-black/5 dark:border-white/5"></div>
+			{/if}
+			<div class="px-4 py-3 {footerClass}">
+				{@render footer()}
+			</div>
+		{/if}
+	</div>
+{/if}

+ 73 - 62
packages/stdf/src/lib/components/cell/Cell.svelte

@@ -2,6 +2,7 @@
 	import Icon from '../icon/Icon.svelte';
 	import Switch from '../switch/Switch.svelte';
 	import type { CellProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
 
 	let {
 		title = '',
@@ -11,9 +12,10 @@
 		subTitle = '',
 		info = '',
 		line = false,
+		bg = 'surface',
 		my = '4',
 		mx = '2',
-		radius = 'lg',
+		radius = '',
 		switchActive = $bindable(false),
 		shadow = 'xs',
 		injClass = '',
@@ -25,16 +27,22 @@
 		onclick
 	}: CellProps = $props();
 
-	// 圆角风格样式
-	// Rounded style style
-	const radiusObj = {
-		none: ' rounded-none',
-		sm: ' rounded-sm',
-		md: ' rounded-md',
-		lg: ' rounded-lg',
-		xl: ' rounded-xl',
-		'2xl': ' rounded-2xl',
-		full: ' rounded-full'
+	// 背景样式
+	// Background style
+	const bgClass = {
+		white: 'bg-white dark:bg-gray-800',
+		surface: 'bg-bg-surface dark:bg-bg-surface-dark',
+		gray: 'bg-text-primary/5 dark:bg-text-dark/5',
+		theme: 'bg-primary/5 dark:bg-dark/5'
+	};
+
+	// 激活背景样式
+	// Active background style
+	const activeBgClass = {
+		white: 'active:bg-black/5 dark:active:bg-white/5',
+		surface: 'active:bg-text-primary/5 dark:active:bg-text-dark/5',
+		gray: 'active:bg-text-primary/10 dark:active:bg-text-dark/10',
+		theme: 'active:bg-primary/10 dark:active:bg-dark/10'
 	};
 
 	// 阴影风格样式
@@ -88,61 +96,64 @@
 </script>
 
 <div
-	class="bg-white px-3 dark:bg-gray-800{clickAll ? ' active:bg-gray-100 dark:active:bg-gray-600' : ''} {myClass[my] ||
-		myClass['4']}{mxClass[mx] || mxClass['2']}{radiusObj[radius] || radiusObj['lg']} {shadowClass[shadow] || shadowClass['xs']}{love
-		? ' text-xl'
-		: ''} {injClass}"
+	class="relative overflow-hidden {myClass[my] || myClass['4']}{mxClass[mx] || mxClass['2']} {radius ? radiusObj[radius] : 'rounded-(--radius-box)'} {shadowClass[shadow] || shadowClass['xs']} {injClass}"
 >
-	<button
-		onclick={setClickFun}
-		class="flex w-full items-center justify-between gap-4 py-4{line && my === '0' ? ' border-b border-black/5 dark:border-white/5' : ''}"
+	<!-- 主内容区域 -->
+	<!-- Main content area -->
+	<div
+		class="{bgClass[bg] || bgClass['surface']} px-3{clickAll ? ` ${activeBgClass[bg] || activeBgClass['surface']}` : ''}{love ? ' text-xl' : ''}"
 	>
-		<div class="flex items-center justify-between">
-			{#if leftChild}
-				{@render leftChild?.()}
-			{:else if left}
-				<div class="mr-1 flex flex-col justify-center">
-					<Icon {...left} />
+		<button
+			onclick={setClickFun}
+			class="flex w-full items-center justify-between gap-4 py-4{line && my === '0' ? ' border-b border-text-primary/5 dark:border-text-dark/5' : ''}"
+		>
+			<div class="flex items-center justify-between">
+				{#if leftChild}
+					{@render leftChild?.()}
+				{:else if left}
+					<div class="mr-1 flex flex-col justify-center">
+						<Icon {...left} />
+					</div>
+				{:else}{/if}
+				<div class="flex flex-col text-left {subTitle === '' ? 'justify-center' : 'justify-between'}">
+					<div class="font-medium">{title}</div>
+					<div class="text-xs text-gray-500 dark:text-gray-400">{subTitle}</div>
 				</div>
-			{:else}{/if}
-			<div class="flex flex-col text-left {subTitle === '' ? 'justify-center' : 'justify-between'}">
-				<div class="font-medium">{title}</div>
-				<div class="text-xs text-gray-500 dark:text-gray-400">{subTitle}</div>
 			</div>
-		</div>
-		<div class="11 flex items-center justify-between">
-			<div class="flex flex-col {info === '' ? 'justify-center' : 'justify-between'} text-right">
-				{#if detailChild}
-					{@render detailChild?.()}
-				{:else if detail}
-					<div class="text-gray-700 dark:text-gray-300">{detail}</div>
+			<div class="11 flex items-center justify-between">
+				<div class="flex flex-col {info === '' ? 'justify-center' : 'justify-between'} text-right">
+					{#if detailChild}
+						{@render detailChild?.()}
+					{:else if detail}
+						<div class="text-gray-700 dark:text-gray-300">{detail}</div>
+					{:else}{/if}
+					<div class="text-xs font-light text-gray-500 dark:text-gray-400">{info}</div>
+				</div>
+				{#if rightChild}
+					{@render rightChild?.()}
+				{:else if right === 'arrow'}
+					<div class="flex flex-col justify-center text-gray-700 dark:text-gray-300">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 24 24"
+							class="fill-current opacity-60"
+							width={love ? 26 : 20}
+							height={love ? 26 : 20}
+						>
+							<path d="M13.1714 12.0007L8.22168 7.05093L9.63589 5.63672L15.9999 12.0007L9.63589 18.3646L8.22168 16.9504L13.1714 12.0007Z"
+							></path>
+						</svg>
+					</div>
+				{:else if typeof right === 'object' && right?.type === 'switch'}
+					<div class="ml-1 flex flex-col justify-center">
+						<Switch active={switchActive} {...right?.switch} />
+					</div>
+				{:else if typeof right === 'object' && right?.type === 'icon' && right?.icon}
+					<div class="ml-1 flex flex-col justify-center">
+						<Icon {...right?.icon} />
+					</div>
 				{:else}{/if}
-				<div class="text-xs font-light text-gray-500 dark:text-gray-400">{info}</div>
 			</div>
-			{#if rightChild}
-				{@render rightChild?.()}
-			{:else if right === 'arrow'}
-				<div class="flex flex-col justify-center text-gray-700 dark:text-gray-300">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 24 24"
-						class="fill-current opacity-60"
-						width={love ? 26 : 20}
-						height={love ? 26 : 20}
-					>
-						<path d="M13.1714 12.0007L8.22168 7.05093L9.63589 5.63672L15.9999 12.0007L9.63589 18.3646L8.22168 16.9504L13.1714 12.0007Z"
-						></path>
-					</svg>
-				</div>
-			{:else if typeof right === 'object' && right?.type === 'switch'}
-				<div class="ml-1 flex flex-col justify-center">
-					<Switch active={switchActive} {...right?.switch} />
-				</div>
-			{:else if typeof right === 'object' && right?.type === 'icon' && right?.icon}
-				<div class="ml-1 flex flex-col justify-center">
-					<Icon {...right?.icon} />
-				</div>
-			{:else}{/if}
-		</div>
-	</button>
+		</button>
+	</div>
 </div>

+ 3 - 11
packages/stdf/src/lib/components/cell/CellGroup.svelte

@@ -1,7 +1,8 @@
 <script lang="ts">
 	import type { CellGroupProps } from '../../types/index.js';
+	import { radiusObj, bgObj } from '../utils/index.js';
 
-	let { radius = 'lg', shadow = 'xs', my = '4', mx = '2', children }: CellGroupProps = $props();
+	let { radius = '', shadow = 'xs', bg = 'surface', my = '4', mx = '2', children }: CellGroupProps = $props();
 
 	const shadowClass = {
 		none: ' shadow-none',
@@ -12,21 +13,12 @@
 		xl: ' shadow-xl dark:shadow-white/10',
 		'2xl': ' shadow-2xl dark:shadow-white/25'
 	};
-	const radiusClass = {
-		none: ' rounded-none',
-		sm: ' rounded-sm',
-		md: ' rounded-md',
-		lg: ' rounded-lg',
-		xl: ' rounded-xl',
-		'2xl': ' rounded-2xl',
-		full: ' rounded-full'
-	};
 	const mxClass = { '0': ' mx-0', '1': ' mx-1', '2': ' mx-2', '3': ' mx-3', '4': ' mx-4', '6': ' mx-6', '8': ' mx-8' };
 	const myClass = { '0': ' my-0', '1': ' my-1', '2': ' my-2', '3': ' my-3', '4': ' my-4', '6': ' my-6', '8': ' my-8' };
 </script>
 
 <div
-	class="bg-white dark:bg-black overflow-hidden{shadowClass[shadow] || shadowClass['xs']}{radiusClass[radius] || radiusClass['lg']}{myClass[
+	class="{bgObj[bg] || bgObj['gray']} overflow-hidden{shadowClass[shadow] || shadowClass['xs']} {radius ? radiusObj[radius] : 'rounded-(--radius-box)'}{myClass[
 		my
 	] || myClass['4']}{mxClass[mx] || mxClass['2']}"
 >

+ 550 - 0
packages/stdf/src/lib/components/charRoll/CharRoll.svelte

@@ -0,0 +1,550 @@
+<script lang="ts">
+	import { onMount, onDestroy } from 'svelte';
+	import type { CharRollProps, CharRollPreset } from '../../types/index.js';
+	import * as easing from 'svelte/easing';
+
+	let {
+		value = '',
+		duration = 1000,
+		delay = 0,
+		stagger = 50,
+		direction = 'up',
+		height = 40,
+		separator = false,
+		decimal,
+		prefix = '',
+		suffix = '',
+		charSet,
+		preset = 'number',
+		loops = 1,
+		autoStart = true,
+		loop = false,
+		loopInterval = 3000,
+		easing: easingType = 'cubicOut',
+		radius = 'sm',
+		bg = 'none',
+		gap = '1',
+		fontSize = 'xl',
+		fontWeight = 'bold',
+		injClass = '',
+		charClass = '',
+		children,
+		onstart,
+		oncomplete,
+		onchange
+	}: CharRollProps = $props();
+
+	// 预设字符集映射
+	// Preset character set mapping
+	const presetCharSets: Record<CharRollPreset, string> = {
+		number: '0123456789',
+		letter: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+		letterUpper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+		letterLower: 'abcdefghijklmnopqrstuvwxyz',
+		alphanumeric: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+		hex: '0123456789abcdef',
+		hexUpper: '0123456789ABCDEF',
+		binary: '01',
+		octal: '01234567'
+	};
+
+	// 获取实际使用的字符集
+	// Get actual character set
+	const getActualCharSet = (): string => {
+		if (charSet) return charSet;
+		return presetCharSets[preset] || presetCharSets.number;
+	};
+
+	// 圆角样式映射
+	// Radius style mapping
+	const radiusClass: Record<string, string> = {
+		none: 'rounded-none',
+		xs: 'rounded-xs',
+		sm: 'rounded-sm',
+		md: 'rounded-md',
+		lg: 'rounded-lg',
+		xl: 'rounded-xl',
+		'2xl': 'rounded-2xl',
+		full: 'rounded-full',
+		'': ''
+	};
+
+	// 背景色样式映射
+	// Background style mapping
+	const bgClass: Record<string, string> = {
+		none: '',
+		surface: 'bg-bg-surface dark:bg-bg-surface-dark',
+		gray: 'bg-gray-100 dark:bg-gray-800',
+		theme: 'bg-primary/10 dark:bg-dark/10'
+	};
+
+	// 间距样式映射
+	// Gap style mapping
+	const gapClass: Record<string, string> = {
+		'0': 'gap-0',
+		'1': 'gap-1',
+		'2': 'gap-2',
+		'3': 'gap-3',
+		'4': 'gap-4'
+	};
+
+	// 字体大小样式映射
+	// Font size style mapping
+	const fontSizeClass: Record<string, string> = {
+		xs: 'text-xs',
+		sm: 'text-sm',
+		base: 'text-base',
+		lg: 'text-lg',
+		xl: 'text-xl',
+		'2xl': 'text-2xl',
+		'3xl': 'text-3xl',
+		'4xl': 'text-4xl'
+	};
+
+	// 字体粗细样式映射
+	// Font weight style mapping
+	const fontWeightClass: Record<string, string> = {
+		normal: 'font-normal',
+		medium: 'font-medium',
+		semibold: 'font-semibold',
+		bold: 'font-bold'
+	};
+
+	// 格式化数值
+	// Format number value
+	const formatValue = (val: string | number): string => {
+		if (typeof val === 'number') {
+			let numStr = decimal !== undefined ? val.toFixed(decimal) : String(val);
+			if (separator) {
+				const parts = numStr.split('.');
+				parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+				numStr = parts.join('.');
+			}
+			return numStr;
+		}
+		return String(val);
+	};
+
+	// 当前显示的字符数组(包含前缀后缀)
+	// Current display characters array (including prefix and suffix)
+	let displayChars = $state<string[]>([]);
+
+	// 每个字符的动画进度 (0-1)
+	// Animation progress for each character (0-1)
+	let animationProgress = $state<number[]>([]);
+
+	// 每个字符的起始索引(动画开始时显示的字符在字符集中的索引)
+	// Starting index for each character (character index in charset when animation starts)
+	let startIndexes = $state<number[]>([]);
+
+	// 每个字符的目标索引
+	// Target index for each character
+	let targetIndexes = $state<number[]>([]);
+
+	// 是否正在动画中
+	// Whether animating
+	let isAnimating = $state(false);
+	let charStarted = $state<boolean[]>([]);
+
+	// 动画帧 ID
+	// Animation frame ID
+	let rafId: number | null = null;
+
+	// 循环定时器
+	// Loop timer
+	let loopTimer: ReturnType<typeof setTimeout> | null = null;
+
+	// 上一次的值
+	// Previous value
+	let prevValue = '';
+
+	// 获取缓动函数
+	// Get easing function
+	const getEasingFn = () => {
+		return easing[easingType] || easing.cubicOut;
+	};
+
+	// 将字符集转为数组(支持 Unicode 字符)
+	// Convert charset to array (support Unicode characters)
+	const getCharSetArray = (): string[] => {
+		return Array.from(getActualCharSet());
+	};
+
+	// 解析字符到字符集的索引
+	// Parse character to charset index
+	const getCharIndex = (char: string): number => {
+		const charSetArray = getCharSetArray();
+		const idx = charSetArray.indexOf(char);
+		return idx >= 0 ? idx : 0;
+	};
+
+	// 检查字符是否在字符集中
+	// Check if character is in charset
+	const isCharInSet = (char: string): boolean => {
+		return getCharSetArray().includes(char);
+	};
+
+	const getAutoStartIndex = (targetIdx: number, charIndex: number, charSetLength: number): number => {
+		if (charSetLength <= 1) return targetIdx;
+		const offset = (charIndex % (charSetLength - 1)) + 1;
+		return (targetIdx + offset) % charSetLength;
+	};
+
+	// 获取当前显示的字符索引
+	// Get current displayed character index
+	const getCurrentDisplayIndex = (charIndex: number): number => {
+		const char = displayChars[charIndex];
+		if (!isCharInSet(char)) return 0;
+
+		const progress = animationProgress[charIndex] || 0;
+		const charSetLength = getCharSetArray().length;
+		const startIdx = startIndexes[charIndex] || 0;
+		const targetIdx = targetIndexes[charIndex] || 0;
+
+		// 计算滚动距离
+		let scrollDistance = targetIdx - startIdx;
+		if (direction === 'up') {
+			if (scrollDistance <= 0) scrollDistance += charSetLength;
+			scrollDistance += charSetLength * Math.max(0, loops - 1);
+		} else {
+			if (scrollDistance >= 0) scrollDistance -= charSetLength;
+			scrollDistance -= charSetLength * Math.max(0, loops - 1);
+		}
+
+		const currentScroll = progress * scrollDistance;
+		const currentIdx = (startIdx + Math.round(currentScroll) % charSetLength + charSetLength) % charSetLength;
+		return currentIdx;
+	};
+
+	// 更新显示字符
+	// Update display characters
+	const updateDisplayChars = () => {
+		const formattedValue = formatValue(value);
+		const fullValue = prefix + formattedValue + suffix;
+		// 使用 Array.from 正确处理 Unicode 字符(如表情符号)
+		// Use Array.from to correctly handle Unicode characters (like emoji)
+		const newDisplayChars = Array.from(fullValue);
+		const newTargetIndexes = newDisplayChars.map((char) => getCharIndex(char));
+
+		// 保存当前显示的字符索引作为新动画的起始索引
+		// Save current displayed character indexes as starting indexes for new animation
+		const newStartIndexes = newDisplayChars.map((char, i) => {
+			if (i < displayChars.length && animationProgress[i] !== undefined) {
+				return getCurrentDisplayIndex(i);
+			}
+			if (autoStart && isCharInSet(char)) {
+				const charSetLength = getCharSetArray().length;
+				const targetIdx = newTargetIndexes[i] ?? 0;
+				return getAutoStartIndex(targetIdx, i, charSetLength);
+			}
+			// 新增的字符位置,从目标字符开始(不滚动)
+			return newTargetIndexes[i];
+		});
+
+		displayChars = newDisplayChars;
+		targetIndexes = newTargetIndexes;
+		startIndexes = newStartIndexes;
+		animationProgress = displayChars.map(() => 0);
+		charStarted = displayChars.map(() => false);
+	};
+
+	// 开始动画
+	// Start animation
+	const start = () => {
+		if (isAnimating) return;
+
+		reset();
+		isAnimating = true;
+		onstart?.();
+
+		const startTime = Date.now();
+		const easingFn = getEasingFn();
+		const charCount = displayChars.length;
+
+		const animate = () => {
+			const elapsed = Date.now() - startTime - delay;
+			let allComplete = true;
+
+			for (let i = 0; i < charCount; i++) {
+				const charDelay = i * stagger;
+				const charElapsed = elapsed - charDelay;
+
+				if (charElapsed < 0) {
+					animationProgress[i] = 0;
+					charStarted[i] = false;
+					allComplete = false;
+				} else if (charElapsed >= duration) {
+					animationProgress[i] = 1;
+					charStarted[i] = true;
+				} else {
+					animationProgress[i] = easingFn(charElapsed / duration);
+					charStarted[i] = true;
+					allComplete = false;
+				}
+			}
+
+			if (allComplete) {
+				isAnimating = false;
+				rafId = null;
+
+				// 动画完成后,将 startIndexes 更新为 targetIndexes
+				// After animation completes, update startIndexes to targetIndexes
+				startIndexes = [...targetIndexes];
+				charStarted = displayChars.map(() => true);
+
+				oncomplete?.();
+				onchange?.(formatValue(value));
+
+				if (loop && loopInterval > 0) {
+					loopTimer = setTimeout(() => {
+						reset();
+						start();
+					}, loopInterval);
+				}
+			} else {
+				rafId = requestAnimationFrame(animate);
+			}
+		};
+
+		rafId = requestAnimationFrame(animate);
+	};
+
+	// 重置动画
+	// Reset animation
+	const reset = () => {
+		if (rafId !== null) {
+			cancelAnimationFrame(rafId);
+			rafId = null;
+		}
+		isAnimating = false;
+		animationProgress = displayChars.map(() => 0);
+		charStarted = displayChars.map(() => false);
+	};
+
+	// 暂停动画(保存当前位置以便继续)
+	// Pause animation (save current position for resuming)
+	const pause = () => {
+		if (rafId !== null) {
+			cancelAnimationFrame(rafId);
+			rafId = null;
+		}
+		isAnimating = false;
+
+		// 保存当前显示的字符索引到 startIndexes,以便下次 start 时从此位置继续
+		// Save current displayed character indexes to startIndexes for resuming
+		const charSetArray = getCharSetArray();
+		const charSetLength = charSetArray.length;
+		for (let i = 0; i < animationProgress.length; i++) {
+			const char = displayChars[i];
+			if (isCharInSet(char)) {
+				const startIdx = startIndexes[i] ?? 0;
+				const targetIdx = targetIndexes[i] ?? 0;
+				const progress = animationProgress[i] || 0;
+
+				// 计算滚动距离
+				let scrollDistance: number;
+				if (direction === 'up') {
+					scrollDistance = targetIdx - startIdx;
+					if (scrollDistance <= 0) scrollDistance += charSetLength;
+					scrollDistance += charSetLength * Math.max(0, loops - 1);
+				} else {
+					scrollDistance = targetIdx - startIdx;
+					if (scrollDistance >= 0) scrollDistance -= charSetLength;
+					scrollDistance -= charSetLength * Math.max(0, loops - 1);
+				}
+
+				// 计算当前字符索引并对齐到整数位置
+				const currentScroll = progress * scrollDistance;
+				const currentIdx = ((startIdx + Math.round(currentScroll)) % charSetLength + charSetLength) % charSetLength;
+				startIndexes[i] = currentIdx;
+			}
+		}
+		// 重置进度,下次 start 会从新的 startIndexes 开始
+		animationProgress = displayChars.map(() => 0);
+	};
+
+	// 计算字符的滚动偏移
+	// Calculate character scroll offset
+	const getScrollOffset = (charIndex: number, progress: number): number => {
+		const char = displayChars[charIndex];
+
+		// 如果字符不在字符集中(如逗号、点号等),不滚动
+		// If char is not in charset (like comma, dot), don't scroll
+		if (!isCharInSet(char)) {
+			return 0;
+		}
+
+		const charSetArray = getCharSetArray();
+		const charSetLength = charSetArray.length;
+		const startIdx = startIndexes[charIndex] ?? 0;
+		const targetIdx = targetIndexes[charIndex] ?? 0;
+
+		// 计算从 startIdx 到 targetIdx 的滚动距离(包含 loops 圈)
+		// Calculate scroll distance from startIdx to targetIdx (including loops cycles)
+		let scrollDistance: number;
+		if (direction === 'up') {
+			// 向上滚动:数字变大的方向
+			scrollDistance = targetIdx - startIdx;
+			if (scrollDistance <= 0) {
+				scrollDistance += charSetLength;
+			}
+			scrollDistance += charSetLength * Math.max(0, loops - 1);
+		} else {
+			// 向下滚动:数字变小的方向
+			scrollDistance = targetIdx - startIdx;
+			if (scrollDistance >= 0) {
+				scrollDistance -= charSetLength;
+			}
+			scrollDistance -= charSetLength * Math.max(0, loops - 1);
+		}
+
+		// 当前滚动位置
+		const currentScroll = startIdx + progress * scrollDistance;
+		const loopsCount = Math.max(1, loops);
+		const baseOffset = charSetLength * loopsCount;
+
+		return -(baseOffset + currentScroll) * height;
+	};
+
+	// 获取当前显示的字符
+	// Get current display character
+	const getCurrentChar = (charIndex: number, progress: number): string => {
+		const char = displayChars[charIndex];
+
+		// 如果字符不在字符集中,直接显示
+		// If char is not in charset, display directly
+		if (!isCharInSet(char)) {
+			return char;
+		}
+
+		const charSetArray = getCharSetArray();
+		const charSetLength = charSetArray.length;
+		const startIdx = startIndexes[charIndex] ?? 0;
+		const targetIdx = targetIndexes[charIndex] ?? 0;
+
+		// 计算滚动距离
+		let scrollDistance: number;
+		if (direction === 'up') {
+			scrollDistance = targetIdx - startIdx;
+			if (scrollDistance <= 0) scrollDistance += charSetLength;
+			scrollDistance += charSetLength * Math.max(0, loops - 1);
+		} else {
+			scrollDistance = targetIdx - startIdx;
+			if (scrollDistance >= 0) scrollDistance -= charSetLength;
+			scrollDistance -= charSetLength * Math.max(0, loops - 1);
+		}
+
+		const currentScroll = progress * scrollDistance;
+		const currentIdx = ((startIdx + Math.floor(currentScroll)) % charSetLength + charSetLength) % charSetLength;
+
+		return charSetArray[currentIdx];
+	};
+
+	// 计算需要渲染的字符数量
+	// Calculate the number of characters to render
+	const getRenderCount = (): number => {
+		const charSetArray = getCharSetArray();
+		const loopsCount = Math.max(1, loops);
+		return charSetArray.length * (loopsCount * 2 + 1) + 1;
+	};
+
+	// 是否已初始化
+	// Whether initialized
+	let initialized = false;
+
+	// 初始化
+	// Initialize
+	onMount(() => {
+		prevValue = formatValue(value);
+		updateDisplayChars();
+		if (autoStart) {
+			start();
+		} else {
+			// 首次加载时,startIndexes 应该等于 targetIndexes(然后滚动 loops 圈)
+			// On first load, startIndexes equals targetIndexes (then scroll loops cycles)
+			startIndexes = [...targetIndexes];
+		}
+		initialized = true;
+	});
+
+	// 监听 value 变化
+	// Watch value changes
+	$effect(() => {
+		const formattedValue = formatValue(value);
+		if (initialized && formattedValue !== prevValue) {
+			prevValue = formattedValue;
+			updateDisplayChars();
+			if (autoStart) {
+				reset();
+				start();
+			}
+		}
+	});
+
+	onDestroy(() => {
+		if (rafId !== null) {
+			cancelAnimationFrame(rafId);
+		}
+		if (loopTimer !== null) {
+			clearTimeout(loopTimer);
+		}
+	});
+
+	// 暴露方法供外部调用
+	// Expose methods for external use
+	export { start, pause, reset };
+</script>
+
+<div
+	class="inline-flex items-center {gapClass[gap] || gapClass['1']} {fontSizeClass[fontSize] ||
+		fontSizeClass['xl']} {fontWeightClass[fontWeight] || fontWeightClass['bold']}{injClass
+		? ` ${injClass}`
+		: ''}"
+>
+	{#each displayChars as char, index (index)}
+		{@const inCharSet = isCharInSet(char)}
+		{@const progress = animationProgress[index] || 0}
+		{@const hasStarted = !isAnimating || (charStarted[index] ?? false)}
+		{@const charSetArray = getCharSetArray()}
+		{@const charSetLength = charSetArray.length}
+		{@const startIndex = startIndexes[index] ?? targetIndexes[index] ?? 0}
+		{@const standbyChar = inCharSet && charSetLength > 0 ? charSetArray[startIndex % charSetLength] : char}
+		{@const displayChar = hasStarted ? (inCharSet ? getCurrentChar(index, progress) : char) : standbyChar}
+		{@const renderCount = getRenderCount()}
+		<div
+			class="relative overflow-hidden {radiusClass[radius] || ''} {bgClass[bg] || ''}{charClass
+				? ` ${charClass}`
+				: ''}"
+			style="height: {height}px;"
+		>
+			{#if children}
+				{@render children(displayChar, index)}
+			{:else if inCharSet && hasStarted}
+				<!-- 滚动的字符列表 -->
+				<!-- Scrolling character list -->
+				<div
+					class="flex flex-col items-center justify-center transition-none"
+					style="transform: translateY({getScrollOffset(index, progress)}px);"
+				>
+					{#each Array(renderCount) as _, i (i)}
+						<div
+							class="flex items-center justify-center"
+							style="height: {height}px; min-width: {height * 0.6}px;"
+						>
+							{charSetArray[i % charSetArray.length]}
+						</div>
+					{/each}
+				</div>
+			{:else}
+				<!-- 非字符集中的字符(如逗号、点号、空格等)直接显示 -->
+				<!-- Characters not in charset (like comma, dot, space) display directly -->
+				<div
+					class="flex items-center justify-center"
+					style="height: {height}px; min-width: {displayChar === ',' || displayChar === '.' ? height * 0.3 : displayChar === ' ' ? height * 0.4 : height * 0.6}px;"
+				>
+					{displayChar}
+				</div>
+			{/if}
+		</div>
+	{/each}
+</div>

+ 1 - 1
packages/stdf/src/lib/components/checkbox/CheckboxItem.svelte

@@ -43,7 +43,7 @@
 				</path>
 			</svg>
 		{:else}
-			<Icon {...iconChecked} theme />
+			<Icon {...iconChecked} state="theme" />
 		{/if}
 	</div>
 	<div class:hidden={checked}>

+ 366 - 0
packages/stdf/src/lib/components/codeInput/CodeInput.svelte

@@ -0,0 +1,366 @@
+<script lang="ts">
+	import type { CodeInputProps } from '../../types/index.js';
+	import { radiusObj, bgObj } from '../utils/index.js';
+
+	let {
+		value = $bindable(''),
+		length = 6,
+		mask = false,
+		gutter = '2',
+		focused = $bindable(false),
+		type = 'number',
+		inputMode = '',
+		native = false,
+		info = '',
+		errorInfo = '',
+		radius = '',
+		cellSize = 'md',
+		cellStyle = 'box',
+		cellBg = 'gray',
+		cellBorder = 'solid',
+		cursorStyle = 'line',
+		cursorAnimation = 'blink',
+		keyboardVisible = $bindable(false),
+		autoClose = false,
+		autoScroll = true as boolean | number,
+		bold = false,
+		injClass = '',
+		onfinish,
+		onclose,
+		onfocus
+	}: CodeInputProps = $props();
+
+	// 隐藏输入框引用
+	// Hidden input reference
+	let inputRef: HTMLInputElement | null = $state(null);
+
+	// 组件容器引用
+	// Component container reference
+	let containerRef: HTMLDivElement | null = $state(null);
+
+	// 间距 class
+	// Gutter class
+	const gutterClass: Record<string, string> = {
+		'0': 'gap-0',
+		'1': 'gap-1',
+		'2': 'gap-2',
+		'3': 'gap-3',
+		'4': 'gap-4'
+	};
+
+	// 边框风格样式
+	// Border style
+	const borderObj: Record<string, string> = {
+		none: '',
+		solid: 'border-solid',
+		dashed: 'border-dashed',
+		dotted: 'border-dotted'
+	};
+
+	// 单元格大小 class
+	// Cell size class
+	const sizeClass: Record<string, string> = {
+		sm: 'w-10 h-10 text-base',
+		md: 'w-12 h-12 text-xl',
+		lg: 'w-14 h-14 text-2xl'
+	};
+
+	// 线模式单元格大小 class
+	// Line mode cell size class
+	const lineSizeClass: Record<string, string> = {
+		sm: 'w-10 h-10 text-base',
+		md: 'w-12 h-12 text-xl',
+		lg: 'w-14 h-14 text-2xl'
+	};
+
+	// 圆点大小 class
+	// Dot size class
+	const dotSizeClass: Record<string, string> = {
+		sm: 'w-2 h-2',
+		md: 'w-2.5 h-2.5',
+		lg: 'w-3 h-3'
+	};
+
+	// 光标大小 class(竖线模式)
+	// Cursor size class (line mode)
+	const cursorSizeClass: Record<string, string> = {
+		sm: 'h-4',
+		md: 'h-5',
+		lg: 'h-6'
+	};
+
+	// 光标动画 class
+	// Cursor animation class
+	const cursorAnimationClass = $derived(cursorAnimation === 'blink' ? 'animate-[blink_1s_step-end_infinite]' : 'animate-pulse');
+
+	// 加大加粗模式下的文字样式
+	// Bold mode text style
+	const boldTextClass: Record<string, string> = {
+		sm: 'text-xl font-bold',
+		md: 'text-2xl font-bold',
+		lg: 'text-3xl font-bold'
+	};
+
+	// 左侧圆角映射
+	// Left radius mapping
+	const radiusLeftObj: Record<string, string> = {
+		none: 'rounded-l-none',
+		xs: 'rounded-l-xs',
+		sm: 'rounded-l-sm',
+		md: 'rounded-l-md',
+		lg: 'rounded-l-lg',
+		xl: 'rounded-l-xl',
+		'2xl': 'rounded-l-2xl',
+		'3xl': 'rounded-l-3xl',
+		'4xl': 'rounded-l-4xl',
+		full: 'rounded-l-full'
+	};
+
+	// 右侧圆角映射
+	// Right radius mapping
+	const radiusRightObj: Record<string, string> = {
+		none: 'rounded-r-none',
+		xs: 'rounded-r-xs',
+		sm: 'rounded-r-sm',
+		md: 'rounded-r-md',
+		lg: 'rounded-r-lg',
+		xl: 'rounded-r-xl',
+		'2xl': 'rounded-r-2xl',
+		'3xl': 'rounded-r-3xl',
+		'4xl': 'rounded-r-4xl',
+		full: 'rounded-r-full'
+	};
+
+	// 获取圆角
+	// Get border radius
+	const cellRadius = $derived(
+		cellStyle === 'line' ? '' : radius ? radiusObj[radius as keyof typeof radiusObj] : 'rounded-(--radius-form)'
+	);
+
+	// 获取左侧圆角
+	// Get left border radius
+	const cellRadiusLeft = $derived(
+		cellStyle === 'line' ? '' : radius ? radiusLeftObj[radius] : 'rounded-l-(--radius-form)'
+	);
+
+	// 获取右侧圆角
+	// Get right border radius
+	const cellRadiusRight = $derived(
+		cellStyle === 'line' ? '' : radius ? radiusRightObj[radius] : 'rounded-r-(--radius-form)'
+	);
+
+	// 获取单元格数组
+	// Get cell array
+	const cells = $derived(Array.from({ length }, (_, i) => i));
+
+	// 获取当前输入位置
+	// Get current input position
+	const currentIndex = $derived(value.length);
+
+	// 是否显示光标(键盘收起时不闪烁)
+	// Whether to show cursor (no blink when keyboard is hidden)
+	const showCursor = $derived(keyboardVisible && currentIndex < length);
+
+	// 是否有错误
+	// Whether has error
+	const hasError = $derived(!!errorInfo);
+
+	// 单元格点击事件
+	// Cell click event
+	const handleClick = () => {
+		focused = true;
+		if (native && inputRef) {
+			inputRef.focus();
+		}
+		onfocus?.();
+	};
+
+	// 原生输入处理
+	// Native input handler
+	const handleInput = (e: Event) => {
+		const target = e.target as HTMLInputElement;
+		let newValue = target.value;
+		// 数字模式下过滤非数字
+		// Filter non-numbers in number mode
+		if (type === 'number') {
+			newValue = newValue.replace(/[^0-9]/g, '');
+		}
+		// 限制长度
+		// Limit length
+		value = newValue.slice(0, length);
+	};
+
+	// 原生输入失焦
+	// Native input blur
+	const handleBlur = () => {
+		focused = false;
+	};
+
+	// 监听输入完成
+	// Watch input finish
+	$effect(() => {
+		if (value.length >= length) {
+			onfinish?.(value.slice(0, length));
+			// 自动关闭键盘
+			// Auto close keyboard
+			if (autoClose) {
+				keyboardVisible = false;
+				focused = false;
+				onclose?.();
+			}
+		}
+	});
+
+	// 限制值长度(非原生模式)
+	// Limit value length (non-native mode)
+	$effect(() => {
+		if (!native) {
+			if (value.length > length) {
+				value = value.slice(0, length);
+			}
+			// 数字模式下过滤非数字
+			// Filter non-numbers in number mode
+			if (type === 'number') {
+				value = value.replace(/[^0-9]/g, '');
+			}
+		}
+	});
+
+	// 键盘弹出时自动滚动页面,避免键盘遮挡输入区域
+	// Auto scroll page when keyboard opens to avoid keyboard covering input area
+	$effect(() => {
+		const shouldScroll = autoScroll;
+		const isKeyboardVisible = keyboardVisible;
+		const container = containerRef;
+
+		if (shouldScroll && isKeyboardVisible && container) {
+			// 延迟执行,等待键盘动画完成
+			// Delay execution to wait for keyboard animation
+			const timer = setTimeout(() => {
+				const rect = container.getBoundingClientRect();
+				const viewportHeight = window.innerHeight;
+				// 键盘高度:如果传入数字则使用,否则默认视口高度的 40%
+				// Keyboard height: use number if provided, otherwise default to 40% of viewport
+				const keyboardHeight = typeof autoScroll === 'number' ? autoScroll : viewportHeight * 0.4;
+				const availableHeight = viewportHeight - keyboardHeight;
+
+				// 如果元素底部超出可用区域,则滚动页面
+				// Scroll page if element bottom exceeds available area
+				if (rect.bottom > availableHeight) {
+					const scrollAmount = rect.bottom - availableHeight + 20; // 20px 额外间距
+					window.scrollTo({
+						top: window.scrollY + scrollAmount,
+						behavior: 'smooth'
+					});
+				}
+			}, 300);
+
+			return () => clearTimeout(timer);
+		}
+	});
+</script>
+
+<div bind:this={containerRef} class="relative {injClass}">
+	<!-- 原生隐藏输入框 -->
+	<!-- Native hidden input -->
+	{#if native}
+		<input
+			bind:this={inputRef}
+			{value}
+			oninput={handleInput}
+			onblur={handleBlur}
+			type="text"
+			inputmode={inputMode || (type === 'number' ? 'numeric' : 'text')}
+			maxlength={length}
+			autocomplete="one-time-code"
+			class="absolute -left-2499.75 opacity-0 w-px h-px"
+		/>
+	{/if}
+
+	<!-- 输入格子 -->
+	<!-- Input cells -->
+	<button
+		class="flex {gutterClass[gutter] || 'gap-2'}"
+		onclick={handleClick}
+		aria-label="code input"
+	>
+		{#each cells as index (index)}
+			{#if cellStyle === 'line'}
+				<!-- 线模式 -->
+				<!-- Line mode -->
+				<div
+					class="relative flex items-center justify-center {lineSizeClass[cellSize] || 'w-12 h-12 text-xl'} font-semibold transition-colors"
+				>
+					{#if index < value.length}
+						{#if mask === true}
+							<span class="bg-text-primary dark:bg-text-dark rounded-full {dotSizeClass[cellSize] || 'w-2.5 h-2.5'}"></span>
+						{:else if typeof mask === 'string'}
+							<span class="text-text-primary dark:text-text-dark {bold ? boldTextClass[cellSize] || 'text-2xl font-bold' : ''}">{mask}</span>
+						{:else}
+							<span class="text-text-primary dark:text-text-dark {bold ? boldTextClass[cellSize] || 'text-2xl font-bold' : ''}">{value[index]}</span>
+						{/if}
+					{:else if showCursor && index === currentIndex}
+						{#if cursorStyle === 'underline'}
+							<span class="absolute bottom-1 left-1/2 -translate-x-1/2 w-1/2 h-0.5 bg-primary dark:bg-dark {cursorAnimationClass}"></span>
+						{:else}
+							<span class="w-0.5 bg-primary dark:bg-dark {cursorSizeClass[cellSize] || 'h-5'} {cursorAnimationClass}"></span>
+						{/if}
+					{/if}
+					<!-- 底部线条 -->
+					<!-- Bottom line -->
+					<span
+						class="absolute bottom-0 left-0 right-0 h-0.5 transition-colors {hasError
+							? 'bg-error dark:bg-error-dark'
+							: focused && index === currentIndex
+								? 'bg-primary dark:bg-dark'
+								: 'bg-text-primary/30 dark:bg-text-dark/30'}"
+					></span>
+				</div>
+			{:else}
+				<!-- 方格模式 -->
+				<!-- Box mode -->
+				<div
+					class="relative flex items-center justify-center {bgObj[cellBg] || bgObj['gray']} {cellBorder !== 'none' ? 'border' : ''} {borderObj[cellBorder] || ''} {hasError
+						? 'border-error dark:border-error-dark'
+						: focused && index === currentIndex
+							? 'border-primary dark:border-dark'
+							: cellBorder !== 'none'
+								? 'border-black/10 dark:border-white/10'
+								: ''} {gutter === '0' && index > 0 && !(focused && index === currentIndex)
+						? 'border-l-0'
+						: ''} {gutter === '0'
+						? index === 0
+							? cellRadiusLeft
+							: index === length - 1
+								? cellRadiusRight
+								: ''
+						: cellRadius} {sizeClass[cellSize] || 'w-12 h-12 text-xl'} font-semibold transition-colors"
+				>
+					{#if index < value.length}
+						{#if mask === true}
+							<span class="bg-text-primary dark:bg-text-dark rounded-full {dotSizeClass[cellSize] || 'w-2.5 h-2.5'}"></span>
+						{:else if typeof mask === 'string'}
+							<span class="text-text-primary dark:text-text-dark {bold ? boldTextClass[cellSize] || 'text-2xl font-bold' : ''}">{mask}</span>
+						{:else}
+							<span class="text-text-primary dark:text-text-dark {bold ? boldTextClass[cellSize] || 'text-2xl font-bold' : ''}">{value[index]}</span>
+						{/if}
+					{:else if showCursor && index === currentIndex}
+						{#if cursorStyle === 'underline'}
+							<span class="absolute bottom-2 left-1/2 -translate-x-1/2 w-1/2 h-0.5 bg-primary dark:bg-dark {cursorAnimationClass}"></span>
+						{:else}
+							<span class="w-0.5 bg-primary dark:bg-dark {cursorSizeClass[cellSize] || 'h-5'} {cursorAnimationClass}"></span>
+						{/if}
+					{/if}
+				</div>
+			{/if}
+		{/each}
+	</button>
+
+	<!-- 提示信息 -->
+	<!-- Info text -->
+	{#if info || errorInfo}
+		<p class="mt-2 text-sm {hasError ? 'text-error dark:text-error-dark' : 'text-gray-500 dark:text-gray-400'}">
+			{errorInfo || info}
+		</p>
+	{/if}
+</div>

+ 1042 - 0
packages/stdf/src/lib/components/colorPicker/ColorPicker.svelte

@@ -0,0 +1,1042 @@
+<script lang="ts">
+	import type { ColorPickerProps, OklchColor, ColorPickerMode, ColorPickerValue } from '../../types/index.js';
+	import Popup from '../popup/Popup.svelte';
+	import Tab from '../tabs/Tab.svelte';
+	import {
+		throttleWithRAF,
+		radiusObj,
+		rgbToOklch,
+		oklchToRgb,
+		rgbToHex,
+		hexToRgb,
+		isDisplayable,
+		getMaxChroma,
+		hslToRgb,
+		rgbToHsl,
+		formatOklch,
+		formatRgb
+	} from '../utils/index.js';
+
+	// 获取主题颜色作为默认值
+	// Get theme color as default value
+	const getThemeColor = (): OklchColor => {
+		if (typeof window === 'undefined') return { l: 0.7, c: 0.15, h: 250 };
+		const isDark = document.documentElement.getAttribute('data-mode') === 'dark';
+		const colorVar = isDark ? '--color-dark' : '--color-primary';
+		const colorValue = getComputedStyle(document.documentElement).getPropertyValue(colorVar).trim();
+		if (colorValue) {
+			// 尝试解析颜色值
+			// Try to parse color value
+			try {
+				// 如果是 oklch 格式
+				// If oklch format
+				if (colorValue.startsWith('oklch(')) {
+					const match = colorValue.match(/oklch\(\s*([\d.]+)%?\s+([\d.]+)\s+([\d.]+)/);
+					if (match) {
+						const l = parseFloat(match[1]) > 1 ? parseFloat(match[1]) / 100 : parseFloat(match[1]);
+						return { l, c: parseFloat(match[2]), h: parseFloat(match[3]) };
+					}
+				}
+				// 如果是 rgb 格式
+				// If rgb format
+				if (colorValue.startsWith('rgb')) {
+					const match = colorValue.match(/rgba?\(\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)/);
+					if (match) {
+						const oklch = rgbToOklch(parseInt(match[1]), parseInt(match[2]), parseInt(match[3]));
+						return { l: oklch.l, c: oklch.c, h: oklch.h || 250 };
+					}
+				}
+				// 如果是 hex 格式
+				// If hex format
+				if (colorValue.startsWith('#')) {
+					const rgb = hexToRgb(colorValue);
+					const oklch = rgbToOklch(rgb[0], rgb[1], rgb[2]);
+					return { l: oklch.l, c: oklch.c, h: oklch.h || 250 };
+				}
+			} catch {
+				// 解析失败,使用默认值
+				// Parse failed, use default value
+			}
+		}
+		return { l: 0.7, c: 0.15, h: 250 };
+	};
+
+	let {
+		visible = $bindable(false),
+		value = $bindable<ColorPickerValue | undefined>(undefined),
+		modes = ['oklch', 'rgb', 'hex'],
+		showPreview = true,
+		showPanel = true,
+		showInputs = true,
+		showCopy = true,
+		panelHeight = 160,
+		sliderHeight = 24,
+		radius = 'md',
+		injClass = '',
+		popup = {},
+		tab = {},
+		onchange,
+		onclose,
+		oncopy
+	}: ColorPickerProps = $props();
+
+	// 有效模式列表
+	// Effective modes list
+	const effectiveModes: ColorPickerMode[] = $derived(modes && modes.length > 0 ? modes : ['oklch']);
+
+	// 将传入的值转换为内部 OKLCH 格式
+	// Convert input value to internal OKLCH format
+	const parseInputValue = (val: ColorPickerValue | undefined): OklchColor => {
+		// 如果未传入值,使用主题颜色
+		// If no value passed, use theme color
+		if (val === undefined) {
+			return getThemeColor();
+		}
+		// 如果是 OKLCH 对象格式
+		// If OKLCH object format
+		if (typeof val === 'object' && val !== null && 'l' in val && 'c' in val && 'h' in val) {
+			return val as OklchColor;
+		}
+		// 如果是 RGB 数组格式 [r, g, b]
+		// If RGB array format [r, g, b]
+		if (Array.isArray(val) && val.length === 3) {
+			const oklch = rgbToOklch(val[0], val[1], val[2]);
+			return { l: oklch.l, c: oklch.c, h: oklch.h || 250 };
+		}
+		// 如果是 HEX 字符串格式
+		// If HEX string format
+		if (typeof val === 'string') {
+			const rgb = hexToRgb(val);
+			const oklch = rgbToOklch(rgb[0], rgb[1], rgb[2]);
+			return { l: oklch.l, c: oklch.c, h: oklch.h || 250 };
+		}
+		// 默认使用主题颜色
+		// Default to theme color
+		return getThemeColor();
+	};
+
+	// 内部 OKLCH 颜色状态
+	// Internal OKLCH color state
+	let internalColor = $state<OklchColor>(parseInputValue(value));
+
+	// 当外部 value 变化时同步内部状态
+	// Sync internal state when external value changes
+	$effect(() => {
+		const parsed = parseInputValue(value);
+		// 只有当值真正变化时才更新(避免循环)
+		// Only update when value actually changes (avoid loop)
+		if (parsed.l !== internalColor.l || parsed.c !== internalColor.c || parsed.h !== internalColor.h) {
+			internalColor = parsed;
+		}
+	});
+
+	// 根据首选模式生成对应格式的输出值
+	// Generate output value in format matching the first mode
+	const getOutputValue = (color: OklchColor): ColorPickerValue => {
+		const primaryMode = effectiveModes[0];
+		if (primaryMode === 'rgb') {
+			return oklchToRgb(color.l, color.c, color.h);
+		} else if (primaryMode === 'hex') {
+			return rgbToHex(...oklchToRgb(color.l, color.c, color.h));
+		}
+		return color;
+	};
+
+	// 容器宽度(自适应)
+	// Container width (adaptive)
+	let containerWidth = $state(0);
+	let panelWidth = $derived(containerWidth > 0 ? containerWidth : 280);
+
+	// 滑动条宽度(由于标签和输入框占据空间,滑动条实际宽度小于面板宽度)
+	// Slider width (actual width is less than panel width due to labels and inputs)
+	let sliderWidth = $state(0);
+
+	// Canvas refs
+	let panelCanvas = $state<HTMLCanvasElement | null>(null);
+	let slider1Canvas = $state<HTMLCanvasElement | null>(null);
+	let slider2Canvas = $state<HTMLCanvasElement | null>(null);
+	let slider3Canvas = $state<HTMLCanvasElement | null>(null);
+
+	// 交互状态
+	// Interaction state
+	let isDraggingPanel = $state(false);
+	let isDragging1 = $state(false);
+	let isDragging2 = $state(false);
+	let isDragging3 = $state(false);
+
+	// 当前模式索引
+	// Current mode index
+	let activeModeIndex = $state(0);
+
+	// 当前模式
+	// Current mode
+	let colorMode = $derived<ColorPickerMode>(effectiveModes[activeModeIndex] || 'oklch');
+
+	// 输入框临时值
+	// Input temporary values
+	let input1 = $state('');
+	let input2 = $state('');
+	let input3 = $state('');
+
+	// 复制提示
+	// Copy tooltip
+	let showCopyTip = $state(false);
+
+	// 设备像素比
+	// Device pixel ratio
+	const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
+
+	// 圆形色轮尺寸(用于 RGB/HEX 模式)
+	// Circular color wheel size (for RGB/HEX mode)
+	const wheelSize = $derived(Math.min(panelWidth, panelHeight));
+
+	// 派生值
+	// Derived values
+	let currentRgb = $derived(oklchToRgb(internalColor.l, internalColor.c, internalColor.h));
+	let currentHex = $derived(rgbToHex(...currentRgb));
+	let currentOklch = $derived(formatOklch(internalColor.l, internalColor.c, internalColor.h));
+	let currentRgbStr = $derived(formatRgb(...currentRgb));
+	let maxChroma = $derived(getMaxChroma(internalColor.l, internalColor.h));
+
+	// 是否直接显示模式
+	// Whether direct display mode
+	const isDirectMode = $derived(popup === null);
+
+	// Tab 标签
+	// Tab labels
+	const tabLabels = $derived(
+		effectiveModes.map((mode) => ({
+			text: mode.toUpperCase()
+		}))
+	);
+
+	// 生成颜色值字符串数组
+	// Generate color value string array
+	const getColorStrings = (): string[] => {
+		return effectiveModes.map((mode) => {
+			if (mode === 'oklch') {
+				return currentOklch;
+			} else if (mode === 'rgb') {
+				return currentRgbStr;
+			} else {
+				return currentHex;
+			}
+		});
+	};
+
+	// 同步输入框值
+	// Sync input values
+	$effect(() => {
+		if (colorMode === 'oklch') {
+			input1 = internalColor.l.toFixed(3);
+			input2 = internalColor.c.toFixed(3);
+			input3 = internalColor.h.toFixed(1);
+		} else if (colorMode === 'rgb') {
+			input1 = currentRgb[0].toString();
+			input2 = currentRgb[1].toString();
+			input3 = currentRgb[2].toString();
+		} else {
+			input1 = currentRgb[0].toString(16).padStart(2, '0');
+			input2 = currentRgb[1].toString(16).padStart(2, '0');
+			input3 = currentRgb[2].toString(16).padStart(2, '0');
+		}
+	});
+
+	// 更新颜色
+	// Update color
+	const updateColor = (newValue: OklchColor) => {
+		internalColor = newValue;
+		// 同步外部 value
+		// Sync external value
+		value = getOutputValue(newValue);
+		// 直接显示模式下,每次变化都触发 onchange
+		// In direct mode, trigger onchange on every change
+		if (isDirectMode) {
+			onchange?.(getColorStrings());
+		}
+	};
+
+	// 从 RGB 更新颜色
+	// Update color from RGB
+	const updateFromRgb = (r: number, g: number, b: number) => {
+		const oklch = rgbToOklch(r, g, b);
+		updateColor({ l: oklch.l, c: oklch.c, h: oklch.h || internalColor.h });
+	};
+
+	// Popup 关闭处理
+	// Popup close handler
+	const handlePopupClose = () => {
+		visible = false;
+		onclose?.(getColorStrings());
+	};
+
+	// ==================== Canvas 绘制 ====================
+	// ==================== Canvas Drawing ====================
+
+	// 绘制颜色面板
+	// Draw color panel
+	const drawPanel = () => {
+		if (!panelCanvas || panelWidth <= 0) return;
+
+		if (colorMode === 'oklch') {
+			// OKLCH 模式:矩形 L-C 平面
+			// OKLCH mode: rectangular L-C plane
+			panelCanvas.width = panelWidth * dpr;
+			panelCanvas.height = panelHeight * dpr;
+			const ctx = panelCanvas.getContext('2d');
+			if (!ctx) return;
+
+			const w = panelWidth * dpr;
+			const h = panelHeight * dpr;
+			const imageData = ctx.createImageData(w, h);
+			const data = imageData.data;
+
+			for (let y = 0; y < h; y++) {
+				const l = 1 - y / h;
+				for (let x = 0; x < w; x++) {
+					const c = (x / w) * 0.4;
+					const idx = (y * w + x) * 4;
+
+					if (isDisplayable(l, c, internalColor.h)) {
+						const [r, g, b] = oklchToRgb(l, c, internalColor.h);
+						data[idx] = r;
+						data[idx + 1] = g;
+						data[idx + 2] = b;
+						data[idx + 3] = 255;
+					} else {
+						// 超出色域显示棋盘格
+						// Show checkerboard for out-of-gamut colors
+						const checkSize = 4 * dpr;
+						const isLight = (Math.floor(x / checkSize) + Math.floor(y / checkSize)) % 2 === 0;
+						const gray = isLight ? 200 : 180;
+						data[idx] = gray;
+						data[idx + 1] = gray;
+						data[idx + 2] = gray;
+						data[idx + 3] = 255;
+					}
+				}
+			}
+			ctx.putImageData(imageData, 0, 0);
+		} else {
+			// RGB/HEX 模式:圆形色轮
+			// RGB/HEX mode: circular color wheel
+			panelCanvas.width = wheelSize * dpr;
+			panelCanvas.height = wheelSize * dpr;
+			const ctx = panelCanvas.getContext('2d');
+			if (!ctx) return;
+
+			const size = wheelSize * dpr;
+			const radius = size / 2;
+			const imageData = ctx.createImageData(size, size);
+			const data = imageData.data;
+
+			const [, , currentL] = rgbToHsl(...currentRgb);
+			const edgeWidth = 1.5 * dpr;
+
+			for (let y = 0; y < size; y++) {
+				for (let x = 0; x < size; x++) {
+					const idx = (y * size + x) * 4;
+					const dx = x - radius;
+					const dy = y - radius;
+					const dist = Math.sqrt(dx * dx + dy * dy);
+
+					if (dist <= radius + edgeWidth) {
+						const angle = Math.atan2(dy, dx);
+						const h = ((angle * 180) / Math.PI + 360) % 360;
+						const s = Math.min(dist / radius, 1);
+						const [r, g, b] = hslToRgb(h, s, currentL);
+						data[idx] = r;
+						data[idx + 1] = g;
+						data[idx + 2] = b;
+
+						if (dist > radius - edgeWidth) {
+							const alpha = Math.max(0, 1 - (dist - radius + edgeWidth) / (edgeWidth * 2));
+							data[idx + 3] = Math.round(alpha * 255);
+						} else {
+							data[idx + 3] = 255;
+						}
+					} else {
+						data[idx + 3] = 0;
+					}
+				}
+			}
+			ctx.putImageData(imageData, 0, 0);
+		}
+	};
+
+	// 绘制滑动条 1 (OKLCH: L / RGB: R)
+	// Draw slider 1 (OKLCH: L / RGB: R)
+	const drawSlider1 = () => {
+		if (!slider1Canvas || sliderWidth <= 0) return;
+		slider1Canvas.width = sliderWidth * dpr;
+		slider1Canvas.height = sliderHeight * dpr;
+		const ctx = slider1Canvas.getContext('2d');
+		if (!ctx) return;
+
+		const w = sliderWidth * dpr;
+		const h = sliderHeight * dpr;
+		const imageData = ctx.createImageData(w, h);
+		const data = imageData.data;
+
+		for (let x = 0; x < w; x++) {
+			let r = 0,
+				g = 0,
+				b = 0;
+
+			if (colorMode === 'oklch') {
+				const l = x / w;
+				if (isDisplayable(l, internalColor.c, internalColor.h)) {
+					[r, g, b] = oklchToRgb(l, internalColor.c, internalColor.h);
+				} else {
+					const mc = getMaxChroma(l, internalColor.h);
+					[r, g, b] = oklchToRgb(l, Math.min(internalColor.c, mc), internalColor.h);
+				}
+			} else {
+				r = Math.round((x / w) * 255);
+				g = 0;
+				b = 0;
+			}
+
+			for (let y = 0; y < h; y++) {
+				const idx = (y * w + x) * 4;
+				data[idx] = r;
+				data[idx + 1] = g;
+				data[idx + 2] = b;
+				data[idx + 3] = 255;
+			}
+		}
+		ctx.putImageData(imageData, 0, 0);
+	};
+
+	// 绘制滑动条 2 (OKLCH: C / RGB: G)
+	// Draw slider 2 (OKLCH: C / RGB: G)
+	const drawSlider2 = () => {
+		if (!slider2Canvas || sliderWidth <= 0) return;
+		slider2Canvas.width = sliderWidth * dpr;
+		slider2Canvas.height = sliderHeight * dpr;
+		const ctx = slider2Canvas.getContext('2d');
+		if (!ctx) return;
+
+		const w = sliderWidth * dpr;
+		const h = sliderHeight * dpr;
+		const imageData = ctx.createImageData(w, h);
+		const data = imageData.data;
+
+		for (let x = 0; x < w; x++) {
+			let r = 0,
+				g = 0,
+				b = 0;
+
+			if (colorMode === 'oklch') {
+				const c = (x / w) * 0.4;
+				if (isDisplayable(internalColor.l, c, internalColor.h)) {
+					[r, g, b] = oklchToRgb(internalColor.l, c, internalColor.h);
+				} else {
+					r = g = b = 180;
+				}
+			} else {
+				r = 0;
+				g = Math.round((x / w) * 255);
+				b = 0;
+			}
+
+			for (let y = 0; y < h; y++) {
+				const idx = (y * w + x) * 4;
+				data[idx] = r;
+				data[idx + 1] = g;
+				data[idx + 2] = b;
+				data[idx + 3] = 255;
+			}
+		}
+		ctx.putImageData(imageData, 0, 0);
+	};
+
+	// 绘制滑动条 3 (OKLCH: H / RGB: B)
+	// Draw slider 3 (OKLCH: H / RGB: B)
+	const drawSlider3 = () => {
+		if (!slider3Canvas || sliderWidth <= 0) return;
+		slider3Canvas.width = sliderWidth * dpr;
+		slider3Canvas.height = sliderHeight * dpr;
+		const ctx = slider3Canvas.getContext('2d');
+		if (!ctx) return;
+
+		const w = sliderWidth * dpr;
+		const h = sliderHeight * dpr;
+		const imageData = ctx.createImageData(w, h);
+		const data = imageData.data;
+
+		for (let x = 0; x < w; x++) {
+			let r = 0,
+				g = 0,
+				b = 0;
+
+			if (colorMode === 'oklch') {
+				// H 滑动条使用固定的 L 和 C 值
+				// H slider uses fixed L and C values
+				const hue = (x / w) * 360;
+				const fixedL = 0.7;
+				const fixedC = 0.15;
+				if (isDisplayable(fixedL, fixedC, hue)) {
+					[r, g, b] = oklchToRgb(fixedL, fixedC, hue);
+				} else {
+					const mc = getMaxChroma(fixedL, hue);
+					[r, g, b] = oklchToRgb(fixedL, Math.min(fixedC, mc), hue);
+				}
+			} else {
+				r = 0;
+				g = 0;
+				b = Math.round((x / w) * 255);
+			}
+
+			for (let y = 0; y < h; y++) {
+				const idx = (y * w + x) * 4;
+				data[idx] = r;
+				data[idx + 1] = g;
+				data[idx + 2] = b;
+				data[idx + 3] = 255;
+			}
+		}
+		ctx.putImageData(imageData, 0, 0);
+	};
+
+	// ==================== 交互处理 ====================
+	// ==================== Interaction Handling ====================
+
+	// 面板交互
+	// Panel interaction
+	const handlePanelInteraction = (e: PointerEvent) => {
+		if (!panelCanvas) return;
+		const rect = panelCanvas.getBoundingClientRect();
+		const clientX = e.clientX;
+		const clientY = e.clientY;
+
+		if (colorMode === 'oklch') {
+			const x = Math.max(0, Math.min(panelWidth, clientX - rect.left));
+			const y = Math.max(0, Math.min(panelHeight, clientY - rect.top));
+			const newL = 1 - y / panelHeight;
+			const newC = (x / panelWidth) * 0.4;
+			const mc = getMaxChroma(newL, internalColor.h);
+			updateColor({ l: newL, c: Math.min(newC, mc), h: internalColor.h });
+		} else {
+			const centerX = wheelSize / 2;
+			const centerY = wheelSize / 2;
+			const dx = clientX - rect.left - centerX;
+			const dy = clientY - rect.top - centerY;
+			const dist = Math.sqrt(dx * dx + dy * dy);
+			const r = wheelSize / 2;
+
+			const clampedDist = Math.min(dist, r);
+			const angle = Math.atan2(dy, dx);
+			const hue = ((angle * 180) / Math.PI + 360) % 360;
+			const saturation = clampedDist / r;
+
+			const [, , currentL] = rgbToHsl(...currentRgb);
+			const [nr, ng, nb] = hslToRgb(hue, saturation, currentL);
+			updateFromRgb(nr, ng, nb);
+		}
+	};
+
+	// 滑动条 1 交互
+	// Slider 1 interaction
+	const handleSlider1Interaction = (e: PointerEvent) => {
+		if (!slider1Canvas) return;
+		const rect = slider1Canvas.getBoundingClientRect();
+		const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
+
+		if (colorMode === 'oklch') {
+			const newL = x / rect.width;
+			const mc = getMaxChroma(newL, internalColor.h);
+			updateColor({ l: newL, c: Math.min(internalColor.c, mc), h: internalColor.h });
+		} else {
+			const newR = Math.round((x / rect.width) * 255);
+			updateFromRgb(newR, currentRgb[1], currentRgb[2]);
+		}
+	};
+
+	// 滑动条 2 交互
+	// Slider 2 interaction
+	const handleSlider2Interaction = (e: PointerEvent) => {
+		if (!slider2Canvas) return;
+		const rect = slider2Canvas.getBoundingClientRect();
+		const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
+
+		if (colorMode === 'oklch') {
+			const newC = (x / rect.width) * 0.4;
+			const mc = getMaxChroma(internalColor.l, internalColor.h);
+			updateColor({ l: internalColor.l, c: Math.min(newC, mc), h: internalColor.h });
+		} else {
+			const newG = Math.round((x / rect.width) * 255);
+			updateFromRgb(currentRgb[0], newG, currentRgb[2]);
+		}
+	};
+
+	// 滑动条 3 交互
+	// Slider 3 interaction
+	const handleSlider3Interaction = (e: PointerEvent) => {
+		if (!slider3Canvas) return;
+		const rect = slider3Canvas.getBoundingClientRect();
+		const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
+
+		if (colorMode === 'oklch') {
+			const newH = (x / rect.width) * 360;
+			const mc = getMaxChroma(internalColor.l, newH);
+			updateColor({ l: internalColor.l, c: Math.min(internalColor.c, mc), h: newH });
+		} else {
+			const newB = Math.round((x / rect.width) * 255);
+			updateFromRgb(currentRgb[0], currentRgb[1], newB);
+		}
+	};
+
+	// Pointer 事件处理
+	// Pointer event handlers
+	const onPanelPointerDown = (e: PointerEvent) => {
+		if (!panelCanvas) return;
+		isDraggingPanel = true;
+		panelCanvas.setPointerCapture(e.pointerId);
+		handlePanelInteraction(e);
+	};
+	const onPanelPointerMove = (e: PointerEvent) => {
+		if (!isDraggingPanel) return;
+		throttleWithRAF(handlePanelInteraction)(e);
+	};
+	const onPanelPointerUp = (e: PointerEvent) => {
+		isDraggingPanel = false;
+		if (panelCanvas?.hasPointerCapture(e.pointerId)) {
+			panelCanvas.releasePointerCapture(e.pointerId);
+		}
+	};
+
+	const onSlider1PointerDown = (e: PointerEvent) => {
+		if (!slider1Canvas) return;
+		isDragging1 = true;
+		slider1Canvas.setPointerCapture(e.pointerId);
+		handleSlider1Interaction(e);
+	};
+	const onSlider1PointerMove = (e: PointerEvent) => {
+		if (!isDragging1) return;
+		throttleWithRAF(handleSlider1Interaction)(e);
+	};
+	const onSlider1PointerUp = (e: PointerEvent) => {
+		isDragging1 = false;
+		if (slider1Canvas?.hasPointerCapture(e.pointerId)) {
+			slider1Canvas.releasePointerCapture(e.pointerId);
+		}
+	};
+
+	const onSlider2PointerDown = (e: PointerEvent) => {
+		if (!slider2Canvas) return;
+		isDragging2 = true;
+		slider2Canvas.setPointerCapture(e.pointerId);
+		handleSlider2Interaction(e);
+	};
+	const onSlider2PointerMove = (e: PointerEvent) => {
+		if (!isDragging2) return;
+		throttleWithRAF(handleSlider2Interaction)(e);
+	};
+	const onSlider2PointerUp = (e: PointerEvent) => {
+		isDragging2 = false;
+		if (slider2Canvas?.hasPointerCapture(e.pointerId)) {
+			slider2Canvas.releasePointerCapture(e.pointerId);
+		}
+	};
+
+	const onSlider3PointerDown = (e: PointerEvent) => {
+		if (!slider3Canvas) return;
+		isDragging3 = true;
+		slider3Canvas.setPointerCapture(e.pointerId);
+		handleSlider3Interaction(e);
+	};
+	const onSlider3PointerMove = (e: PointerEvent) => {
+		if (!isDragging3) return;
+		throttleWithRAF(handleSlider3Interaction)(e);
+	};
+	const onSlider3PointerUp = (e: PointerEvent) => {
+		isDragging3 = false;
+		if (slider3Canvas?.hasPointerCapture(e.pointerId)) {
+			slider3Canvas.releasePointerCapture(e.pointerId);
+		}
+	};
+
+	// ==================== 输入框处理 ====================
+	// ==================== Input Handling ====================
+
+	const onBlur1 = () => {
+		if (colorMode === 'oklch') {
+			const val = parseFloat(input1);
+			if (!isNaN(val) && val >= 0 && val <= 1) {
+				const mc = getMaxChroma(val, internalColor.h);
+				updateColor({ l: val, c: Math.min(internalColor.c, mc), h: internalColor.h });
+			}
+		} else if (colorMode === 'rgb') {
+			const val = parseInt(input1);
+			if (!isNaN(val) && val >= 0 && val <= 255) {
+				updateFromRgb(val, currentRgb[1], currentRgb[2]);
+			}
+		} else {
+			const val = parseInt(input1, 16);
+			if (!isNaN(val) && val >= 0 && val <= 255) {
+				updateFromRgb(val, currentRgb[1], currentRgb[2]);
+			}
+		}
+	};
+
+	const onBlur2 = () => {
+		if (colorMode === 'oklch') {
+			const val = parseFloat(input2);
+			if (!isNaN(val) && val >= 0 && val <= 0.4) {
+				const mc = getMaxChroma(internalColor.l, internalColor.h);
+				updateColor({ l: internalColor.l, c: Math.min(val, mc), h: internalColor.h });
+			}
+		} else if (colorMode === 'rgb') {
+			const val = parseInt(input2);
+			if (!isNaN(val) && val >= 0 && val <= 255) {
+				updateFromRgb(currentRgb[0], val, currentRgb[2]);
+			}
+		} else {
+			const val = parseInt(input2, 16);
+			if (!isNaN(val) && val >= 0 && val <= 255) {
+				updateFromRgb(currentRgb[0], val, currentRgb[2]);
+			}
+		}
+	};
+
+	const onBlur3 = () => {
+		if (colorMode === 'oklch') {
+			const val = parseFloat(input3);
+			if (!isNaN(val)) {
+				const newH = ((val % 360) + 360) % 360;
+				const mc = getMaxChroma(internalColor.l, newH);
+				updateColor({ l: internalColor.l, c: Math.min(internalColor.c, mc), h: newH });
+			}
+		} else if (colorMode === 'rgb') {
+			const val = parseInt(input3);
+			if (!isNaN(val) && val >= 0 && val <= 255) {
+				updateFromRgb(currentRgb[0], currentRgb[1], val);
+			}
+		} else {
+			const val = parseInt(input3, 16);
+			if (!isNaN(val) && val >= 0 && val <= 255) {
+				updateFromRgb(currentRgb[0], currentRgb[1], val);
+			}
+		}
+	};
+
+	const onKeydown = (e: KeyboardEvent, blurFn: () => void) => {
+		if (e.key === 'Enter') {
+			blurFn();
+		}
+	};
+
+	// ==================== 复制功能 ====================
+	// ==================== Copy Functionality ====================
+
+	const copyToClipboard = async (text: string) => {
+		try {
+			await navigator.clipboard.writeText(text);
+			oncopy?.(text);
+			showCopyTip = true;
+			setTimeout(() => {
+				showCopyTip = false;
+			}, 1500);
+		} catch (err) {
+			console.error('Copy failed:', err);
+		}
+	};
+
+	// ==================== 位置计算 ====================
+	// ==================== Position Calculation ====================
+
+	const getSlider1Position = () => {
+		if (colorMode === 'oklch') {
+			return internalColor.l * sliderWidth;
+		} else {
+			return (currentRgb[0] / 255) * sliderWidth;
+		}
+	};
+
+	const getSlider2Position = () => {
+		if (colorMode === 'oklch') {
+			const mc = getMaxChroma(internalColor.l, internalColor.h);
+			const displayC = Math.min(internalColor.c, mc);
+			return (displayC / 0.4) * sliderWidth;
+		} else {
+			return (currentRgb[1] / 255) * sliderWidth;
+		}
+	};
+
+	const getSlider3Position = () => {
+		if (colorMode === 'oklch') {
+			return (internalColor.h / 360) * sliderWidth;
+		} else {
+			return (currentRgb[2] / 255) * sliderWidth;
+		}
+	};
+
+	const getPanelPosition = (): { x: number; y: number } => {
+		if (colorMode === 'oklch') {
+			const mc = getMaxChroma(internalColor.l, internalColor.h);
+			const displayC = Math.min(internalColor.c, mc);
+			return {
+				x: (displayC / 0.4) * panelWidth,
+				y: (1 - internalColor.l) * panelHeight
+			};
+		} else {
+			const [hue, saturation] = rgbToHsl(...currentRgb);
+			const r = wheelSize / 2;
+			const angle = (hue * Math.PI) / 180;
+			const dist = saturation * r;
+			return {
+				x: r + Math.cos(angle) * dist,
+				y: r + Math.sin(angle) * dist
+			};
+		}
+	};
+
+	const getLabels = (): [string, string, string] => {
+		if (colorMode === 'oklch') {
+			return ['L', 'C', 'H'];
+		} else {
+			return ['R', 'G', 'B'];
+		}
+	};
+
+	// ==================== 响应式绘制 ====================
+	// ==================== Reactive Drawing ====================
+
+	// 监听 visible 变化,重新打开时延迟绘制
+	// Watch visible change, delay drawing when reopened
+	$effect(() => {
+		if (visible && panelWidth > 0 && sliderWidth > 0) {
+			// 使用 setTimeout 确保 canvas 已渲染
+			// Use setTimeout to ensure canvas is rendered
+			setTimeout(() => {
+				drawPanel();
+				drawSlider1();
+				drawSlider2();
+				drawSlider3();
+			}, 50);
+		}
+	});
+
+	$effect(() => {
+		if (panelWidth > 0) {
+			drawPanel();
+		}
+	});
+
+	// 滑动条宽度变化时重绘
+	// Redraw when slider width changes
+	$effect(() => {
+		if (sliderWidth > 0) {
+			drawSlider1();
+			drawSlider2();
+			drawSlider3();
+		}
+	});
+
+	$effect(() => {
+		internalColor.h;
+		internalColor.l;
+		internalColor.c;
+		colorMode;
+		if (panelWidth > 0) {
+			drawPanel();
+		}
+		if (sliderWidth > 0) {
+			drawSlider1();
+			drawSlider2();
+			drawSlider3();
+		}
+	});
+</script>
+
+{#snippet pickerContent()}
+	<div class="w-full select-none px-6 py-4 {injClass}">
+		<div bind:clientWidth={containerWidth}></div>
+		<!-- 颜色预览 -->
+		<!-- Color preview -->
+		{#if showPreview}
+			<div class="mb-3 flex items-center gap-3">
+				<div
+					class="h-10 w-10 flex-none {radius && radiusObj[radius as keyof typeof radiusObj] ? radiusObj[radius as keyof typeof radiusObj] : 'rounded-md'} border border-black/10 dark:border-white/20"
+					style="background-color: {currentHex}"
+				></div>
+				<div class="relative flex-1 text-xs">
+					{#if showCopy}
+						<button
+							onclick={() => copyToClipboard(currentHex)}
+							class="mb-0.5 w-full cursor-pointer rounded px-1 py-0.5 text-left font-mono transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/20"
+						>
+							{currentHex}
+						</button>
+						<button
+							onclick={() => copyToClipboard(currentRgbStr)}
+							class="mb-0.5 w-full cursor-pointer rounded px-1 py-0.5 text-left font-mono transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/20"
+						>
+							{currentRgbStr}
+						</button>
+						<button
+							onclick={() => copyToClipboard(currentOklch)}
+							class="w-full cursor-pointer rounded px-1 py-0.5 text-left font-mono transition-colors hover:bg-black/5 active:bg-black/10 dark:hover:bg-white/10 dark:active:bg-white/20"
+						>
+							{currentOklch}
+						</button>
+					{:else}
+						<div class="mb-0.5 px-1 py-0.5 font-mono">{currentHex}</div>
+						<div class="mb-0.5 px-1 py-0.5 font-mono">{currentRgbStr}</div>
+						<div class="px-1 py-0.5 font-mono">{currentOklch}</div>
+					{/if}
+					{#if showCopyTip}
+						<div
+							class="absolute -top-1 right-0 rounded bg-black/80 px-2 py-1 text-xs text-white dark:bg-white/80 dark:text-black"
+						>
+							Copied
+						</div>
+					{/if}
+				</div>
+			</div>
+		{/if}
+
+		<!-- 颜色面板 -->
+		<!-- Color panel -->
+		{#if showPanel}
+			<div class="relative mb-3 {colorMode === 'oklch' ? '' : 'flex justify-center'}">
+				<canvas
+					bind:this={panelCanvas}
+					style="width: {colorMode === 'oklch' ? panelWidth : wheelSize}px; height: {colorMode === 'oklch'
+						? panelHeight
+						: wheelSize}px;"
+					class="cursor-crosshair touch-none {colorMode === 'oklch'
+						? radius && radiusObj[radius as keyof typeof radiusObj] ? radiusObj[radius as keyof typeof radiusObj] : 'rounded-md'
+						: ''} {colorMode === 'oklch' ? 'border border-black/10 dark:border-white/20' : ''}"
+					onpointerdown={onPanelPointerDown}
+					onpointermove={onPanelPointerMove}
+					onpointerup={onPanelPointerUp}
+					onpointercancel={onPanelPointerUp}
+				></canvas>
+				<div
+					class="pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md"
+					style="left: {colorMode === 'oklch'
+						? getPanelPosition().x
+						: (panelWidth - wheelSize) / 2 + getPanelPosition().x}px; top: {getPanelPosition().y}px; background-color: {currentHex}"
+				></div>
+			</div>
+		{/if}
+
+		<!-- 滑动条 1 -->
+		<!-- Slider 1 -->
+		<div class="mb-2 flex items-center gap-2">
+			{#if showInputs}
+				<span class="w-3 flex-none text-xs text-black/60 dark:text-white/60">{getLabels()[0]}</span>
+			{/if}
+			<div class="relative flex-1" bind:clientWidth={sliderWidth}>
+				<canvas
+					bind:this={slider1Canvas}
+					style="height: {sliderHeight}px;"
+					class="w-full cursor-pointer touch-none {radius && radiusObj[radius as keyof typeof radiusObj] ? radiusObj[radius as keyof typeof radiusObj] : 'rounded-md'} border border-black/10 dark:border-white/20"
+					onpointerdown={onSlider1PointerDown}
+					onpointermove={onSlider1PointerMove}
+					onpointerup={onSlider1PointerUp}
+					onpointercancel={onSlider1PointerUp}
+				></canvas>
+				<div
+					class="pointer-events-none absolute top-0 h-full w-3 -translate-x-1/2 rounded-sm bg-white shadow-md"
+					style="left: {getSlider1Position()}px; border: 1px solid rgba(0,0,0,0.3)"
+				></div>
+			</div>
+			{#if showInputs}
+				<input
+					type="text"
+					bind:value={input1}
+					onblur={onBlur1}
+					onkeydown={(e) => onKeydown(e, onBlur1)}
+					class="w-14 flex-none rounded border border-black/10 bg-transparent px-1 py-0.5 text-right font-mono text-xs focus:border-primary focus:outline-none dark:border-white/20 dark:focus:border-dark"
+				/>
+			{/if}
+		</div>
+
+		<!-- 滑动条 2 -->
+		<!-- Slider 2 -->
+		<div class="mb-2 flex items-center gap-2">
+			{#if showInputs}
+				<span class="w-3 flex-none text-xs text-black/60 dark:text-white/60">{getLabels()[1]}</span>
+			{/if}
+			<div class="relative flex-1">
+				<canvas
+					bind:this={slider2Canvas}
+					style="height: {sliderHeight}px;"
+					class="w-full cursor-pointer touch-none {radius && radiusObj[radius as keyof typeof radiusObj] ? radiusObj[radius as keyof typeof radiusObj] : 'rounded-md'} border border-black/10 dark:border-white/20"
+					onpointerdown={onSlider2PointerDown}
+					onpointermove={onSlider2PointerMove}
+					onpointerup={onSlider2PointerUp}
+					onpointercancel={onSlider2PointerUp}
+				></canvas>
+				<div
+					class="pointer-events-none absolute top-0 h-full w-3 -translate-x-1/2 rounded-sm bg-white shadow-md"
+					style="left: {getSlider2Position()}px; border: 1px solid rgba(0,0,0,0.3)"
+				></div>
+			</div>
+			{#if showInputs}
+				<input
+					type="text"
+					bind:value={input2}
+					onblur={onBlur2}
+					onkeydown={(e) => onKeydown(e, onBlur2)}
+					class="w-14 flex-none rounded border border-black/10 bg-transparent px-1 py-0.5 text-right font-mono text-xs focus:border-primary focus:outline-none dark:border-white/20 dark:focus:border-dark"
+				/>
+			{/if}
+		</div>
+
+		<!-- 滑动条 3 -->
+		<!-- Slider 3 -->
+		<div class="mb-3 flex items-center gap-2">
+			{#if showInputs}
+				<span class="w-3 flex-none text-xs text-black/60 dark:text-white/60">{getLabels()[2]}</span>
+			{/if}
+			<div class="relative flex-1">
+				<canvas
+					bind:this={slider3Canvas}
+					style="height: {sliderHeight}px;"
+					class="w-full cursor-pointer touch-none {radius && radiusObj[radius as keyof typeof radiusObj] ? radiusObj[radius as keyof typeof radiusObj] : 'rounded-md'} border border-black/10 dark:border-white/20"
+					onpointerdown={onSlider3PointerDown}
+					onpointermove={onSlider3PointerMove}
+					onpointerup={onSlider3PointerUp}
+					onpointercancel={onSlider3PointerUp}
+				></canvas>
+				<div
+					class="pointer-events-none absolute top-0 h-full w-3 -translate-x-1/2 rounded-sm bg-white shadow-md"
+					style="left: {getSlider3Position()}px; border: 1px solid rgba(0,0,0,0.3)"
+				></div>
+			</div>
+			{#if showInputs}
+				<input
+					type="text"
+					bind:value={input3}
+					onblur={onBlur3}
+					onkeydown={(e) => onKeydown(e, onBlur3)}
+					class="w-14 flex-none rounded border border-black/10 bg-transparent px-1 py-0.5 text-right font-mono text-xs focus:border-primary focus:outline-none dark:border-white/20 dark:focus:border-dark"
+				/>
+			{/if}
+		</div>
+
+		<!-- 模式切换 Tab -->
+		<!-- Mode switch Tab -->
+		{#if effectiveModes.length > 1}
+			<Tab labels={tabLabels} bind:active={activeModeIndex} mx="0" {...tab} />
+		{/if}
+	</div>
+{/snippet}
+
+<!-- 根据 popup 参数决定渲染方式 -->
+<!-- Render based on popup parameter -->
+{#if isDirectMode}
+	<!-- 直接显示模式 -->
+	<!-- Direct display mode -->
+	{@render pickerContent()}
+{:else}
+	<!-- Popup 模式 -->
+	<!-- Popup mode -->
+	<Popup bind:visible size={0} transitionDistance={400} position="bottom" {...popup} onclose={handlePopupClose}>
+		{@render pickerContent()}
+	</Popup>
+{/if}

+ 200 - 0
packages/stdf/src/lib/components/countDown/CountDown.svelte

@@ -0,0 +1,200 @@
+<script lang="ts">
+	import { onMount, onDestroy } from 'svelte';
+	import type { CountDownProps, TimeData } from '../../types/index.js';
+
+	let {
+		time = 0,
+		format = 'HH:mm:ss',
+		autoStart = true,
+		millisecond = false,
+		injClass = '',
+		children,
+		onfinish,
+		onchange
+	}: CountDownProps = $props();
+
+	// 剩余时间(毫秒)
+	// Remaining time (milliseconds)
+	let remain = $derived(time);
+	;
+
+	// 是否正在运行
+	// Whether running
+	let counting = $state(false);
+
+	// 结束时间戳
+	// End timestamp
+	let endTime = 0;
+
+	// 动画帧 ID
+	// Animation frame ID
+	let rafId: number | null = null;
+
+	// 上一次的 time 值,用于检测外部传入的 time 是否变化
+	// Previous time value, used to detect external time prop changes
+	let prevTime = $derived(time);
+	;
+
+	// 解析时间为 TimeData
+	// Parse time to TimeData
+	const parseTime = (ms: number): TimeData => {
+		const days = Math.floor(ms / (1000 * 60 * 60 * 24));
+		const hours = Math.floor((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+		const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
+		const seconds = Math.floor((ms % (1000 * 60)) / 1000);
+		const milliseconds = ms % 1000;
+
+		return { days, hours, minutes, seconds, milliseconds };
+	};
+
+	// 当前时间数据
+	// Current time data
+	const timeData = $derived(parseTime(remain));
+
+	// 补零函数
+	// Pad zero function
+	const padZero = (num: number, length: number = 2): string => {
+		return String(num).padStart(length, '0');
+	};
+
+	// 格式化显示文本
+	// Format display text
+	const formattedTime = $derived(() => {
+		let result = format;
+		const { days, hours, minutes, seconds, milliseconds } = timeData;
+
+		// 处理天数
+		// Handle days
+		if (result.includes('DD')) {
+			result = result.replace('DD', padZero(days));
+		} else {
+			// 如果没有天数格式,将天数加到小时中
+			// If no day format, add days to hours
+		}
+
+		// 计算总小时数(如果格式中没有 DD)
+		// Calculate total hours (if no DD in format)
+		const totalHours = format.includes('DD') ? hours : days * 24 + hours;
+
+		result = result.replace('HH', padZero(totalHours));
+		result = result.replace('mm', padZero(minutes));
+		result = result.replace('ss', padZero(seconds));
+		result = result.replace('SSS', padZero(milliseconds, 3));
+		result = result.replace('SS', padZero(Math.floor(milliseconds / 10), 2));
+		result = result.replace('S', String(Math.floor(milliseconds / 100)));
+
+		return result;
+	});
+
+	// 计时核心函数
+	// Core timing function
+	const tick = () => {
+		const now = Date.now();
+		const newRemain = Math.max(0, endTime - now);
+
+		// 根据是否毫秒级渲染决定是否更新
+		// Decide whether to update based on millisecond rendering
+		if (millisecond || Math.floor(newRemain / 1000) !== Math.floor(remain / 1000)) {
+			remain = newRemain;
+			onchange?.(timeData);
+		} else {
+			remain = newRemain;
+		}
+
+		if (remain > 0) {
+			rafId = requestAnimationFrame(tick);
+		} else {
+			counting = false;
+			onfinish?.();
+		}
+	};
+
+	// 开始倒计时
+	// Start countdown
+	const start = () => {
+		if (counting) return;
+		if (remain <= 0) return;
+
+		counting = true;
+		endTime = Date.now() + remain;
+		tick();
+	};
+
+	// 暂停倒计时
+	// Pause countdown
+	const pause = () => {
+		if (!counting) return;
+
+		counting = false;
+		if (rafId !== null) {
+			cancelAnimationFrame(rafId);
+			rafId = null;
+		}
+		// 暂停时更新剩余时间为当前精确值
+		// Update remain to current exact value when pausing
+		remain = Math.max(0, endTime - Date.now());
+	};
+
+	// 重置倒计时
+	// Reset countdown
+	const reset = (newTime?: number) => {
+		pause();
+		remain = newTime !== undefined ? newTime : time;
+		onchange?.(timeData);
+	};
+
+	// 处理页面可见性变化
+	// Handle page visibility change
+	const handleVisibilityChange = () => {
+		if (document.hidden) {
+			// 页面隐藏时不需要特殊处理,因为我们使用的是绝对时间
+			// No special handling needed when page is hidden, as we use absolute time
+		} else {
+			// 页面可见时,如果正在计时则继续
+			// When page is visible, continue if counting
+			if (counting && rafId === null) {
+				tick();
+			}
+		}
+	};
+
+	onMount(() => {
+		if (autoStart && time > 0) {
+			start();
+		}
+
+		// 监听页面可见性变化
+		// Listen for page visibility changes
+		document.addEventListener('visibilitychange', handleVisibilityChange);
+	});
+
+	onDestroy(() => {
+		if (rafId !== null) {
+			cancelAnimationFrame(rafId);
+		}
+		document.removeEventListener('visibilitychange', handleVisibilityChange);
+	});
+
+	// 监听 time 属性变化(只在外部 time 真正变化时才更新)
+	// Watch time prop changes (only update when external time actually changes)
+	$effect(() => {
+		if (time !== prevTime) {
+			prevTime = time;
+			if (!counting) {
+				remain = time;
+			}
+		}
+	});
+
+	// 暴露方法供外部调用
+	// Expose methods for external use
+	export { start, pause, reset };
+</script>
+
+<span class="inline-block{injClass ? ` ${injClass}` : ''}">
+	{#if children}
+		{@render children(timeData)}
+	{:else}
+		{formattedTime()}
+	{/if}
+</span>

+ 1 - 1
packages/stdf/src/lib/components/dialog/Dialog.svelte

@@ -94,7 +94,7 @@
 			<div class={btnStyle === 'textLine' && btnReverse ? 'border-r border-black/10 dark:border-white/10' : ''} style="flex:{btnRatio[0]}">
 				<Button
 					size="full"
-					fill={btnStyle === 'button' ? 'base' : 'textTheme'}
+					fill={btnStyle === 'button' ? 'base' : 'textState'}
 					heightIn={btnStyle === 'button' ? '3' : '2'}
 					injClass={btnStyle === 'button' ? '' : 'font-bold'}
 					{...primaryButton}

+ 218 - 0
packages/stdf/src/lib/components/feedback/FeedbackContainer.svelte

@@ -0,0 +1,218 @@
+<script lang="ts">
+	// 全局反馈组件容器
+	// Global feedback component container
+	// 使用方式:在应用根布局中添加此组件
+	// Usage: Add this component to your app's root layout
+
+	import { getContext, onMount } from 'svelte';
+	import { feedbackState } from './state.svelte.js';
+	import Toast from '../toast/Toast.svelte';
+	import Dialog from '../dialog/Dialog.svelte';
+	import Modal from '../modal/Modal.svelte';
+	import Alert from '../alert/Alert.svelte';
+	import Loading from '../loading/Loading.svelte';
+	import Mask from '../mask/Mask.svelte';
+	import { zh_CN, type LangProps } from '../../lang/index.js';
+
+	// 从 Context 获取语言配置
+	// Get language configuration from Context
+	const lang = (getContext('STDF_lang') || zh_CN) as LangProps;
+
+	// 同步语言配置到全局状态
+	// Sync language configuration to global state
+	onMount(() => {
+		feedbackState.lang = lang;
+	});
+
+	// 处理 Toast 关闭
+	// Handle Toast close
+	function handleToastClose(id: string) {
+		feedbackState.toastQueue = feedbackState.toastQueue.filter(t => t.id !== id);
+	}
+
+	// 处理 Alert 关闭
+	// Handle Alert close
+	function handleAlertClose(id: string) {
+		feedbackState.alertQueue = feedbackState.alertQueue.filter(a => a.id !== id);
+	}
+
+	// 处理 Dialog 关闭
+	// Handle Dialog close
+	function handleDialogClose() {
+		feedbackState.dialogVisible = false;
+		feedbackState.dialogResolve?.('close');
+	}
+
+	// 处理 Dialog 主按钮点击
+	// Handle Dialog primary button click
+	function handleDialogPrimary() {
+		feedbackState.dialogVisible = false;
+		feedbackState.dialogResolve?.('primary');
+	}
+
+	// 处理 Dialog 次按钮点击
+	// Handle Dialog secondary button click
+	function handleDialogSecondary() {
+		feedbackState.dialogVisible = false;
+		feedbackState.dialogResolve?.('secondary');
+	}
+
+	// 处理 Modal 关闭
+	// Handle Modal close
+	function handleModalClose() {
+		feedbackState.modalVisible = false;
+		feedbackState.modalResolve?.('close');
+	}
+
+	// Toast 堆叠位置计算
+	// Toast stack position calculation
+	function getToastStyle(index: number, position: string): string {
+		const baseGap = 60;
+		const gap = 8;
+		const offset = index * (baseGap + gap);
+
+		if (position === 'top') {
+			return `margin-top: ${offset}px`;
+		} else if (position === 'bottom') {
+			return `margin-bottom: ${offset}px`;
+		}
+		return '';
+	}
+
+	// Alert 堆叠位置计算
+	// Alert stack position calculation
+	function getAlertStyle(index: number, position: string): string {
+		const baseGap = 70;
+		const gap = 8;
+		const offset = index * (baseGap + gap);
+
+		if (position === 'top' || !position) {
+			return `margin-top: ${offset}px`;
+		} else if (position === 'bottom') {
+			return `margin-bottom: ${offset}px`;
+		}
+		return '';
+	}
+</script>
+
+<!-- Toast 队列渲染 -->
+<!-- Toast queue rendering -->
+{#each feedbackState.toastQueue as item, index (item.id)}
+	<div style={getToastStyle(index, item.position || 'center')}>
+		<Toast
+			message={item.message}
+			visible={item.visible}
+			duration={item.duration}
+			position={item.position}
+			py={item.py}
+			radius={item.radius}
+			transitionType={item.transitionType}
+			transitionParams={item.transitionParams}
+			outDuration={item.outDuration}
+			type={item.type}
+			mask={item.mask}
+			loading={item.loading}
+			icon={item.icon}
+			zIndex={1000 + index}
+			clickable={item.clickable}
+			dynamicFixed={item.dynamicFixed}
+			onclose={() => handleToastClose(item.id)}
+		/>
+	</div>
+{/each}
+
+<!-- Alert 堆叠渲染 -->
+<!-- Alert stack rendering -->
+{#each feedbackState.alertQueue as item, index (item.id)}
+	<div style={getAlertStyle(index, item.position || 'top')}>
+		<Alert
+			bind:visible={item.visible}
+			title={item.title}
+			message={item.message}
+			duration={item.duration}
+			position={item.position}
+			py={item.py}
+			type={item.type}
+			showIcon={item.showIcon}
+			icon={item.icon}
+			closable={item.closable}
+			inverse={item.inverse}
+			card={item.card}
+			transitionType={item.transitionType}
+			transitionParams={item.transitionParams}
+			outDuration={item.outDuration}
+			easeType={item.easeType}
+			easeOutType={item.easeOutType}
+			zIndex={1100 + index}
+			clickable={item.clickable}
+			injClass={item.injClass}
+			onclose={() => handleAlertClose(item.id)}
+		/>
+	</div>
+{/each}
+
+<!-- Dialog 单例 -->
+<!-- Dialog singleton -->
+{#if feedbackState.dialogProps && feedbackState.dialogVisible}
+	<Dialog
+		visible={feedbackState.dialogVisible}
+		title={feedbackState.dialogProps.title}
+		titleAlign={feedbackState.dialogProps.titleAlign}
+		content={feedbackState.dialogProps.content}
+		popup={feedbackState.dialogProps.popup}
+		showIcon={feedbackState.dialogProps.showIcon}
+		icon={feedbackState.dialogProps.icon}
+		btnStyle={feedbackState.dialogProps.btnStyle}
+		primaryText={feedbackState.dialogProps.primaryText}
+		primaryButton={feedbackState.dialogProps.primaryButton}
+		secondaryText={feedbackState.dialogProps.secondaryText}
+		secondaryButton={feedbackState.dialogProps.secondaryButton}
+		btnRatio={feedbackState.dialogProps.btnRatio}
+		btnReverse={feedbackState.dialogProps.btnReverse}
+		secondaryClose={feedbackState.dialogProps.secondaryClose}
+		btnGap={feedbackState.dialogProps.btnGap}
+		onprimary={handleDialogPrimary}
+		onsecondary={handleDialogSecondary}
+		onclose={handleDialogClose}
+	/>
+{/if}
+
+<!-- Modal 单例 -->
+<!-- Modal singleton -->
+{#if feedbackState.modalProps && feedbackState.modalVisible}
+	<Modal
+		visible={feedbackState.modalVisible}
+		title={feedbackState.modalProps.title}
+		titleAlign={feedbackState.modalProps.titleAlign}
+		content={feedbackState.modalProps.content}
+		popup={feedbackState.modalProps.popup}
+		showIcon={feedbackState.modalProps.showIcon}
+		icon={feedbackState.modalProps.icon}
+		showBtn={feedbackState.modalProps.showBtn}
+		btnText={feedbackState.modalProps.btnText}
+		button={feedbackState.modalProps.button}
+		onclose={handleModalClose}
+	/>
+{/if}
+
+<!-- Loading 全局遮罩 -->
+<!-- Loading global mask -->
+{#if feedbackState.loadingVisible}
+	<Mask visible={true} opacity="0.5" zIndex={2000}>
+		<div class="flex h-full flex-col items-center justify-center">
+			<Loading
+				type={feedbackState.loadingProps.type}
+				height={feedbackState.loadingProps.height || '16'}
+				width={feedbackState.loadingProps.width || '16'}
+				theme={feedbackState.loadingProps.theme}
+				inverse={feedbackState.loadingProps.inverse}
+				customColor={feedbackState.loadingProps.customColor}
+			/>
+			{#if feedbackState.loadingMessage}
+				<div class="mt-3 text-sm text-white dark:text-black">
+					{feedbackState.loadingMessage}
+				</div>
+			{/if}
+		</div>
+	</Mask>
+{/if}

+ 291 - 0
packages/stdf/src/lib/components/feedback/index.ts

@@ -0,0 +1,291 @@
+/**
+ * 全局反馈组件函数式 API
+ * Global feedback component functional API
+ */
+
+import { feedbackState } from './state.svelte.js';
+import type { DialogResult, ModalResult } from './state.types.js';
+import type { ToastProps, DialogProps, ModalProps, AlertProps, LoadingProps } from '../../types/index.js';
+
+// ==================== Toast API ====================
+
+type ToastOptions = Omit<ToastProps, 'visible' | 'children'>;
+
+/**
+ * 显示 Toast 提示
+ * Show Toast message
+ */
+function showToast(options: ToastOptions | string): string {
+	const props: ToastOptions = typeof options === 'string' ? { message: options } : options;
+	const id = feedbackState.generateId();
+
+	feedbackState.toastQueue = [
+		...feedbackState.toastQueue,
+		{ ...props, id, visible: true }
+	];
+
+	return id;
+}
+
+/**
+ * 隐藏指定 Toast
+ * Hide specific Toast
+ */
+function hideToast(id?: string): void {
+	if (id) {
+		feedbackState.toastQueue = feedbackState.toastQueue.filter(t => t.id !== id);
+	} else if (feedbackState.toastQueue.length > 0) {
+		// 关闭最新的一个
+		// Close the latest one
+		feedbackState.toastQueue = feedbackState.toastQueue.slice(0, -1);
+	}
+}
+
+/**
+ * 清除所有 Toast
+ * Clear all Toasts
+ */
+function clearAllToasts(): void {
+	feedbackState.toastQueue = [];
+}
+
+/**
+ * Toast 函数式 API
+ * Toast functional API
+ */
+export const toast = Object.assign(showToast, {
+	success: (message: string, options?: Omit<ToastOptions, 'message' | 'type'>) =>
+		showToast({ message, type: 'success', ...options }),
+	error: (message: string, options?: Omit<ToastOptions, 'message' | 'type'>) =>
+		showToast({ message, type: 'error', ...options }),
+	warning: (message: string, options?: Omit<ToastOptions, 'message' | 'type'>) =>
+		showToast({ message, type: 'warning', ...options }),
+	info: (message: string, options?: Omit<ToastOptions, 'message' | 'type'>) =>
+		showToast({ message, type: 'info', ...options }),
+	loading: (message: string, options?: Omit<ToastOptions, 'message' | 'type'>) =>
+		showToast({ message, type: 'loading', duration: 0, ...options }),
+	hide: hideToast,
+	clear: clearAllToasts
+});
+
+// ==================== Alert API ====================
+
+type AlertOptions = Omit<AlertProps, 'visible' | 'children'>;
+
+/**
+ * 显示 Alert 提示
+ * Show Alert message
+ */
+function showAlertFn(options: AlertOptions | string): string {
+	const props: AlertOptions = typeof options === 'string' ? { message: options } : options;
+	const id = feedbackState.generateId();
+
+	feedbackState.alertQueue = [
+		...feedbackState.alertQueue,
+		{ ...props, id, visible: true }
+	];
+
+	return id;
+}
+
+/**
+ * 隐藏指定 Alert
+ * Hide specific Alert
+ */
+function hideAlert(id?: string): void {
+	if (id) {
+		feedbackState.alertQueue = feedbackState.alertQueue.filter(a => a.id !== id);
+	} else if (feedbackState.alertQueue.length > 0) {
+		feedbackState.alertQueue = feedbackState.alertQueue.slice(0, -1);
+	}
+}
+
+/**
+ * 清除所有 Alert
+ * Clear all Alerts
+ */
+function clearAllAlerts(): void {
+	feedbackState.alertQueue = [];
+}
+
+/**
+ * Alert 函数式 API(使用 showAlert 避免与 window.alert 冲突)
+ * Alert functional API (use showAlert to avoid conflict with window.alert)
+ */
+export const showAlert = Object.assign(showAlertFn, {
+	success: (message: string, options?: Omit<AlertOptions, 'message' | 'type'>) =>
+		showAlertFn({ message, type: 'success', ...options }),
+	error: (message: string, options?: Omit<AlertOptions, 'message' | 'type'>) =>
+		showAlertFn({ message, type: 'error', ...options }),
+	warning: (message: string, options?: Omit<AlertOptions, 'message' | 'type'>) =>
+		showAlertFn({ message, type: 'warning', ...options }),
+	info: (message: string, options?: Omit<AlertOptions, 'message' | 'type'>) =>
+		showAlertFn({ message, type: 'info', ...options }),
+	hide: hideAlert,
+	clear: clearAllAlerts
+});
+
+// ==================== Dialog API ====================
+
+type DialogOptions = Omit<DialogProps, 'visible' | 'onprimary' | 'onsecondary' | 'onclose' | 'contentChild' | 'primaryChild'>;
+
+/**
+ * 显示 Dialog 对话框
+ * Show Dialog
+ */
+function showDialog(options: DialogOptions): Promise<DialogResult> {
+	return new Promise((resolve) => {
+		feedbackState.dialogProps = {
+			...options,
+			onprimary: () => {
+				feedbackState.dialogVisible = false;
+				resolve('primary');
+			},
+			onsecondary: () => {
+				feedbackState.dialogVisible = false;
+				resolve('secondary');
+			},
+			onclose: () => {
+				feedbackState.dialogVisible = false;
+				resolve('close');
+			}
+		};
+		feedbackState.dialogVisible = true;
+		feedbackState.dialogResolve = resolve;
+	});
+}
+
+/**
+ * 关闭 Dialog
+ * Close Dialog
+ */
+function closeDialog(): void {
+	feedbackState.dialogVisible = false;
+	feedbackState.dialogResolve?.('close');
+}
+
+/**
+ * Dialog 函数式 API
+ * Dialog functional API
+ */
+export const dialog = Object.assign(showDialog, {
+	/**
+	 * 确认对话框快捷方法
+	 * Confirm dialog shortcut
+	 */
+	confirm: (content: string, title?: string): Promise<boolean> => {
+		return showDialog({
+			title: title || feedbackState.lang?.dialog?.title || '提示',
+			content,
+			primaryText: feedbackState.lang?.dialog?.primaryText || '确定',
+			secondaryText: feedbackState.lang?.dialog?.secondaryText || '取消'
+		}).then(result => result === 'primary');
+	},
+	close: closeDialog
+});
+
+// ==================== Modal API ====================
+
+type ModalOptions = Omit<ModalProps, 'visible' | 'onclose' | 'contentChild'>;
+
+/**
+ * 显示 Modal 弹框
+ * Show Modal
+ */
+function showModal(options: ModalOptions): Promise<ModalResult> {
+	return new Promise((resolve) => {
+		feedbackState.modalProps = {
+			...options,
+			onclose: () => {
+				feedbackState.modalVisible = false;
+				resolve('close');
+			}
+		};
+		feedbackState.modalVisible = true;
+		feedbackState.modalResolve = resolve;
+	});
+}
+
+/**
+ * 关闭 Modal
+ * Close Modal
+ */
+function closeModal(): void {
+	feedbackState.modalVisible = false;
+	feedbackState.modalResolve?.('close');
+}
+
+/**
+ * Modal 函数式 API
+ * Modal functional API
+ */
+export const modal = Object.assign(showModal, {
+	/**
+	 * 信息弹框快捷方法
+	 * Info modal shortcut
+	 */
+	info: (content: string, title?: string): Promise<ModalResult> => {
+		return showModal({
+			title: title || feedbackState.lang?.modal?.title || '提示',
+			content,
+			btnText: feedbackState.lang?.modal?.btnText || '我知道了'
+		});
+	},
+	close: closeModal
+});
+
+// ==================== Loading API ====================
+
+type LoadingOptions = Partial<LoadingProps> & { message?: string };
+
+/**
+ * 显示 Loading
+ * Show Loading
+ */
+function showLoadingFn(options?: LoadingOptions | string): void {
+	if (typeof options === 'string') {
+		feedbackState.loadingMessage = options;
+		feedbackState.loadingProps = {};
+	} else if (options) {
+		feedbackState.loadingMessage = options.message || '';
+		const { message, ...rest } = options;
+		feedbackState.loadingProps = rest;
+	} else {
+		feedbackState.loadingMessage = '';
+		feedbackState.loadingProps = {};
+	}
+	feedbackState.loadingVisible = true;
+}
+
+/**
+ * 隐藏 Loading
+ * Hide Loading
+ */
+function hideLoading(): void {
+	feedbackState.loadingVisible = false;
+}
+
+/**
+ * Loading 函数式 API
+ * Loading functional API
+ */
+export const loading = {
+	show: showLoadingFn,
+	hide: hideLoading
+};
+
+// ==================== 配置 API ====================
+
+import type { LangProps } from '../../lang/index.js';
+
+/**
+ * 设置反馈组件语言配置
+ * Set feedback component language configuration
+ */
+export function setFeedbackLang(lang: LangProps): void {
+	feedbackState.lang = lang;
+}
+
+// 导出状态(供 FeedbackContainer 使用)
+// Export state (for FeedbackContainer)
+export { feedbackState };

+ 78 - 0
packages/stdf/src/lib/components/feedback/state.svelte.ts

@@ -0,0 +1,78 @@
+/**
+ * 全局反馈组件状态管理
+ * Global feedback component state management
+ */
+
+import type { ToastItem, AlertItem, DialogResolve, ModalResolve, DialogStateProps, ModalStateProps } from './state.types.js';
+import type { LangProps } from '../../lang/index.js';
+import type { LoadingProps } from '../../types/index.js';
+
+/**
+ * 全局反馈状态类
+ * Global feedback state class
+ */
+class FeedbackState {
+	// Toast 队列
+	// Toast queue
+	toastQueue: ToastItem[] = $state([]);
+
+	// Alert 队列
+	// Alert queue
+	alertQueue: AlertItem[] = $state([]);
+
+	// Dialog 状态
+	// Dialog state
+	dialogVisible = $state(false);
+	dialogProps: DialogStateProps | null = $state(null);
+	dialogResolve: DialogResolve | null = null;
+
+	// Modal 状态
+	// Modal state
+	modalVisible = $state(false);
+	modalProps: ModalStateProps | null = $state(null);
+	modalResolve: ModalResolve | null = null;
+
+	// Loading 状态
+	// Loading state
+	loadingVisible = $state(false);
+	loadingProps: Partial<LoadingProps> = $state({});
+	loadingMessage = $state('');
+
+	// 语言配置
+	// Language configuration
+	lang: LangProps | null = $state(null);
+
+	// ID 计数器
+	// ID counter
+	idCounter = 0;
+
+	/**
+	 * 生成唯一 ID
+	 * Generate unique ID
+	 */
+	generateId() {
+		return `feedback_${++this.idCounter}_${Date.now()}`;
+	}
+
+	/**
+	 * 重置所有状态
+	 * Reset all states
+	 */
+	reset() {
+		this.toastQueue = [];
+		this.alertQueue = [];
+		this.dialogVisible = false;
+		this.dialogProps = null;
+		this.dialogResolve = null;
+		this.modalVisible = false;
+		this.modalProps = null;
+		this.modalResolve = null;
+		this.loadingVisible = false;
+		this.loadingProps = {};
+		this.loadingMessage = '';
+	}
+}
+
+// 导出全局单例
+// Export global singleton
+export const feedbackState = new FeedbackState();

+ 39 - 0
packages/stdf/src/lib/components/feedback/state.types.ts

@@ -0,0 +1,39 @@
+import type { ToastProps, DialogProps, ModalProps, AlertProps } from '../../types/index.js';
+
+// Toast 队列项类型
+// Toast queue item type
+export type ToastItem = Omit<ToastProps, 'children'> & {
+	id: string;
+	visible: boolean;
+};
+
+// Alert 队列项类型
+// Alert queue item type
+export type AlertItem = Omit<AlertProps, 'children'> & {
+	id: string;
+	visible: boolean;
+};
+
+// Dialog 返回结果类型
+// Dialog result type
+export type DialogResult = 'primary' | 'secondary' | 'close';
+
+// Modal 返回结果类型
+// Modal result type
+export type ModalResult = 'confirm' | 'close';
+
+// Dialog Promise resolve 函数类型
+// Dialog Promise resolve function type
+export type DialogResolve = (result: DialogResult) => void;
+
+// Modal Promise resolve 函数类型
+// Modal Promise resolve function type
+export type ModalResolve = (result: ModalResult) => void;
+
+// Dialog 状态类型
+// Dialog state type
+export type DialogStateProps = Omit<DialogProps, 'visible' | 'contentChild' | 'primaryChild'>;
+
+// Modal 状态类型
+// Modal state type
+export type ModalStateProps = Omit<ModalProps, 'visible' | 'contentChild'>;

+ 516 - 276
packages/stdf/src/lib/components/form/Form.svelte

@@ -5,13 +5,17 @@
 	import ActionSheet from '../actionSheet/ActionSheet.svelte';
 	import Calendar from '../calendar/Calendar.svelte';
 	import NumKeyboard from '../numKeyboard/NumKeyboard.svelte';
+	import FullKeyboard from '../fullKeyboard/FullKeyboard.svelte';
 	import Picker from '../picker/Picker.svelte';
 	import Checkbox from '../checkbox/Checkbox.svelte';
 	import Radio from '../radio/Radio.svelte';
 	import Slider from '../slider/Slider.svelte';
 	import Switch from '../switch/Switch.svelte';
 	import Stepper from '../stepper/Stepper.svelte';
+	import ColorPicker from '../colorPicker/ColorPicker.svelte';
+	import Tag from '../tag/Tag.svelte';
 	import Button from '../button/Button.svelte';
+	import Card from '../card/Card.svelte';
 	import { zh_CN, type LangProps } from '../../lang/index.js';
 	import type {
 		FormProps,
@@ -20,13 +24,15 @@
 		FormCalendarValue,
 		FormActionSheetValue,
 		FormPickerValue,
-		FormSliderValue
+		FormSliderValue,
+		FormColorPickerValue
 	} from '../../types/index.js';
 
 	// 当前语言
 	// current language
 	const currentLang = (getContext('STDF_lang') || zh_CN) as LangProps;
 	const formLang: LangProps['form'] = currentLang.form;
+	const inputLang: LangProps['input'] = currentLang.input;
 
 	let {
 		form,
@@ -39,15 +45,18 @@
 		onreset,
 		submitChildren,
 		resetChildren,
-		card = false,
-		mx = '4',
-		space = '0',
-		radius = 'xl',
-		px = '2',
-		shadow = 'sm'
+		card,
+		space = '0'
 	}: FormProps = $props();
 
-	let formInner = $state(
+	const formatColorPickerValue = (value: FormColorPickerValue) => {
+		if (!value) return '';
+		if (typeof value === 'string') return value;
+		if (Array.isArray(value)) return `rgb(${value.join(', ')})`;
+		return `oklch(${value.l} ${value.c} ${value.h})`;
+	};
+
+	let formInner = $derived(
 		form.map((item) => {
 			const baseItem = {
 				showPopup: false,
@@ -95,10 +104,20 @@
 									: item.type === 'actionSheet'
 										? ({ action: undefined, index: undefined } as FormActionSheetValue)
 										: item.type === 'picker'
-											? ({ items: [], indexs: [] } as FormPickerValue)
+											? ({
+													items: [],
+													indexs: [],
+													multipleSelected: item.picker?.multiple
+														? item.picker?.multipleSelected || ((item.initValue as FormPickerValue)?.multipleSelected ?? [])
+														: undefined
+												} as FormPickerValue)
 											: item.type === 'calendar'
 												? ({ dates: [] } as FormCalendarValue)
-												: (item.initValue as string) || ''
+												: item.type === 'fullKeyboard'
+													? (item.fullKeyboard?.value as string) || (item.initValue as string) || ''
+													: item.type === 'colorPicker'
+														? ((item.colorPicker?.value as FormColorPickerValue) ?? (item.initValue as FormColorPickerValue))
+														: (item.initValue as string) || ''
 			};
 		})
 	);
@@ -152,9 +171,19 @@
 			} else if (item.type === 'actionSheet') {
 				item.value = { action: undefined, index: undefined } as FormActionSheetValue;
 			} else if (item.type === 'picker') {
-				item.value = { items: [], indexs: [] } as FormPickerValue;
+				item.value = {
+					items: [],
+					indexs: [],
+					multipleSelected: item.picker?.multiple
+						? item.picker?.multipleSelected || ((item.initValue as FormPickerValue)?.multipleSelected ?? [])
+						: undefined
+				} as FormPickerValue;
 			} else if (item.type === 'calendar') {
 				item.value = { dates: [] } as FormCalendarValue;
+			} else if (item.type === 'fullKeyboard') {
+				item.value = (item.fullKeyboard?.value as string) || (item.initValue as string) || '';
+			} else if (item.type === 'colorPicker') {
+				item.value = (item.colorPicker?.value as FormColorPickerValue) ?? (item.initValue as FormColorPickerValue);
 			} else {
 				item.value = (item.initValue as string) || '';
 			}
@@ -172,284 +201,495 @@
 
 	// 间距 space / spacing
 	const spaceClass = { '0': 'space-y-0', '1': 'space-y-1', '2': 'space-y-2', '4': 'space-y-4', '6': 'space-y-6', '8': 'space-y-8' };
-	// 卡片圆角 card radius / card border radius
-	const radiusClass = {
-		sm: 'rounded-sm',
-		md: 'rounded-md',
-		lg: 'rounded-lg',
-		xl: 'rounded-xl',
-		'2xl': 'rounded-2xl',
-		'3xl': 'rounded-3xl',
-		'4xl': 'rounded-4xl'
-	};
-	// 水平外边距 mx / horizontal margin
-	const mxClass = { '2': 'mx-2', '3': 'mx-3', '4': 'mx-4', '6': 'mx-6', '8': 'mx-8' };
-	// 内边距 px / padding
-	const pxClass = { '0': 'px-0', '1': 'px-1', '2': 'px-2', '4': 'px-4', '6': 'px-6' };
-	// 阴影 shadow / shadow
-	const shadowClass = {
-		none: 'shadow-none',
-		xs: 'shadow-xs dark:shadow-white/10',
-		sm: 'shadow-sm dark:shadow-white/10',
-		md: 'shadow-md dark:shadow-white/10',
-		lg: 'shadow-lg dark:shadow-white/10',
-		xl: 'shadow-xl dark:shadow-white/10',
-		'2xl': 'shadow-2xl dark:shadow-white/10'
-	};
 </script>
 
-<div
-	class="{spaceClass[space] || 'space-y-0'}{card
-		? ` ${radiusClass[radius] || 'rounded-xl'} ${mxClass[mx] || 'mx-4'} ${pxClass[px] || 'px-2'} ${shadowClass[shadow] || shadowClass.sm} mb-2 bg-white py-2 dark:bg-black`
-		: ''}"
->
-	{#each formInner as item (item.name)}
-		{#if item.type === 'input' || !item.type}
-			<Input
-				title={item.label}
-				bind:value={item.value as string}
-				clear
-				required={item.required}
-				onclear={() => {
-					item.value = '';
-					handleFormChange(item, '');
-				}}
-				onchange={() => handleFormChange(item, item.value)}
-				{...'input' in item ? item.input : {}}
-			/>
-		{:else if item.type === 'timePicker'}
-			<Input
-				title={item.label}
-				select
-				value={(item.value as FormTimePickerValue)?.timeStr || ''}
-				clear
-				required={item.required}
-				onfocus={() => {
-					item.showPopup = true;
-				}}
-				onclear={() => {
-					item.value = { timeStr: '', timeObj: undefined };
-					handleFormChange(item, { timeStr: '', timeObj: undefined });
-				}}
-				{...item?.input}
-			/>
-			<TimePicker
-				bind:visible={item.showPopup}
-				onconfirm={(timeStr, timeObj) => {
-					item.value = { timeStr, timeObj };
-					handleFormChange(item, { timeStr, timeObj });
-				}}
-				{...item.timePicker}
-			/>
-		{:else if item.type === 'actionSheet'}
-			<Input
-				title={item.label}
-				select
-				value={(item.value as FormActionSheetValue)?.action?.content || ''}
-				clear
-				required={item.required}
-				onfocus={() => {
-					item.showPopup = true;
-				}}
-				onclear={() => {
-					item.value = { action: undefined, index: undefined };
-					handleFormChange(item, { action: undefined, index: undefined });
-				}}
-				{...item?.input}
-			/>
-			<ActionSheet
-				bind:visible={item.showPopup}
-				onclickAction={(index: number, action) => {
-					item.value = { action, index };
-					handleFormChange(item, { action, index });
-				}}
-				{...item.actionSheet}
-			/>
-		{:else if item.type === 'calendar'}
-			<Input
-				title={item.label}
-				select
-				value={(() => {
-					const dates = (item.value as FormCalendarValue)?.dates;
-					if (!dates || dates.length === 0) return '';
-					// 如果是范围选择,格式为 "开始日期 - 结束日期" / if range selection, format as "start date - end date"
-					const isRange = item.calendar?.mode === 'range';
-					if (isRange && dates.length >= 2) {
-						return `${dates[0]} - ${dates[1]}`;
-					}
-					return dates.join(',');
-				})()}
-				clear
-				required={item.required}
-				onfocus={() => {
-					item.showPopup = true;
-				}}
-				onclear={() => {
-					item.value = { dates: [] };
-					handleFormChange(item, { dates: [] });
-				}}
-				{...item?.input}
-			/>
-			<Calendar
-				bind:visible={item.showPopup}
-				onconfirm={(dates) => {
-					item.value = { dates };
-					handleFormChange(item, { dates });
-				}}
-				clear={false}
-				{...item.calendar}
-			/>
-		{:else if item.type === 'numKeyboard'}
-			<Input
-				title={item.label}
-				select
-				bind:value={item.value as string}
-				clear
-				required={item.required}
-				onfocus={() => {
-					item.showPopup = true;
-				}}
-				onclear={() => {
-					item.value = '';
-					handleFormChange(item, '');
-				}}
-				{...item.input}
-			/>
-			<NumKeyboard
-				bind:visible={item.showPopup}
-				onclick={(key) => {
-					if (key === 'close' || key === 'done') {
-						item.showPopup = false;
-					} else if (key === 'delete') {
-						item.value = (item.value as string).slice(0, -1);
-					} else {
-						item.value = (item.value as string) + key;
-					}
-					handleFormChange(item, item.value);
-				}}
-				{...item.numKeyboard}
-			/>
-		{:else if item.type === 'picker'}
-			<Input
-				title={item.label}
-				select
-				value={(item.value as FormPickerValue)?.items?.map((item) => item.label).join(' / ') || ''}
-				clear
-				required={item.required}
-				onfocus={() => {
-					item.showPopup = true;
-				}}
-				onclear={() => {
-					item.value = { items: [], indexs: [] };
-					handleFormChange(item, { items: [], indexs: [] });
-				}}
-				{...item?.input}
-			/>
-			<Picker
-				bind:visible={item.showPopup}
-				datas={item.picker?.datas || []}
-				onconfirm={(items, indexs) => {
-					item.value = { items, indexs };
-					handleFormChange(item, { items, indexs });
-				}}
-				onclose={() => {
-					item.showPopup = false;
-				}}
-				{...item.picker}
-			/>
-		{:else if item.type === 'checkbox'}
-			<div class="p-2">
-				<div class="mb-2 flex justify-between px-2">
-					<div class="relative mb-1 text-sm font-semibold">
-						{#if item.required}
-							<span class="text-error absolute -left-2.5 text-base">*</span>
-						{/if}
-						{item.label}
-					</div>
-					<div class="flex space-x-2 text-xs"></div>
-				</div>
-				<Checkbox
-					data={item.checkbox?.data || []}
-					{...item.checkbox}
-					bind:checkeds={item.value as string[]}
-					onchange={(checkeds: string[]) => handleFormChange(item, checkeds)}
+{#snippet formContent()}
+	<div class={spaceClass[space] || 'space-y-0'}>
+		{#each formInner as item (item.name)}
+			{#if item.type === 'input' || !item.type}
+				<Input
+					title={item.label}
+					bind:value={item.value as string}
+					clear
+					required={item.required}
+					onclear={() => {
+						item.value = '';
+						handleFormChange(item, '');
+					}}
+					onchange={() => handleFormChange(item, item.value)}
+					{...'input' in item ? item.input : {}}
+				/>
+			{:else if item.type === 'timePicker'}
+				<Input
+					title={item.label}
+					select
+					value={(item.value as FormTimePickerValue)?.timeStr || ''}
+					clear
+					required={item.required}
+					onfocus={() => {
+						item.showPopup = true;
+					}}
+					onclear={() => {
+						item.value = { timeStr: '', timeObj: undefined };
+						handleFormChange(item, { timeStr: '', timeObj: undefined });
+					}}
+					{...item?.input}
 				/>
-			</div>
-		{:else if item.type === 'radio'}
-			<div class="p-2">
-				<div class="mb-2 flex justify-between px-2">
-					<div class="relative mb-1 text-sm font-semibold">
-						{#if item.required}
-							<span class="text-error absolute -left-2.5 text-base">*</span>
-						{/if}
-						{item.label}
+				<TimePicker
+					bind:visible={item.showPopup}
+					onconfirm={(timeStr, timeObj) => {
+						item.value = { timeStr, timeObj };
+						handleFormChange(item, { timeStr, timeObj });
+					}}
+					{...item.timePicker}
+				/>
+			{:else if item.type === 'actionSheet'}
+				<Input
+					title={item.label}
+					select
+					value={(item.value as FormActionSheetValue)?.action?.content || ''}
+					clear
+					required={item.required}
+					onfocus={() => {
+						item.showPopup = true;
+					}}
+					onclear={() => {
+						item.value = { action: undefined, index: undefined };
+						handleFormChange(item, { action: undefined, index: undefined });
+					}}
+					{...item?.input}
+				/>
+				<ActionSheet
+					bind:visible={item.showPopup}
+					onclickAction={(index: number, action) => {
+						item.value = { action, index };
+						handleFormChange(item, { action, index });
+					}}
+					{...item.actionSheet}
+				/>
+			{:else if item.type === 'calendar'}
+				{@const calendarDates = (item.value as FormCalendarValue)?.dates || []}
+				{@const isCalendarMultiple = item.calendar?.mode === 'multiple'}
+				{@const calendarPlaceholder =
+					item.input?.placeholder && item.input.placeholder !== ''
+						? item.input.placeholder
+						: item.label
+							? inputLang.pleaseSelect + ' ' + item.label
+							: ''}
+				{#if isCalendarMultiple}
+					<!-- 多选模式:使用自定义展示容器 -->
+					<div class="px-2 py-2">
+						<div class="flex justify-between px-2">
+							<div class="relative mb-1 text-sm font-semibold">
+								{#if item.required}
+									<span class="text-error absolute -left-2.5 text-base">*</span>
+								{/if}
+								{item.label}
+							</div>
+						</div>
+						<div
+							role="button"
+							tabindex="0"
+							class="bg-text-primary/5 dark:bg-text-dark/5 rounded-(--radius-form) relative my-0.5 flex min-h-12 w-full cursor-pointer items-center gap-2 px-2 py-3 text-left text-sm transition-all duration-300"
+							onclick={() => {
+								item.showPopup = true;
+							}}
+							onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && (item.showPopup = true)}
+						>
+							<div class="flex flex-1 flex-wrap items-center gap-2">
+								{#if (item.value as FormCalendarValue)?.dates && (item.value as FormCalendarValue).dates!.length > 0}
+									{#each (item.value as FormCalendarValue).dates! as date, index (date)}
+										<Tag
+											text={date}
+											size="sm"
+											fill="light"
+											closable
+											onclose={() => {
+												const currentDates = (item.value as FormCalendarValue)?.dates || [];
+												const nextDates = currentDates.filter((_: string, i: number) => i !== index);
+												item.value = { dates: nextDates };
+												handleFormChange(item, { dates: nextDates });
+											}}
+										/>
+									{/each}
+								{:else}
+									<span class="font-semibold opacity-50">{calendarPlaceholder}</span>
+								{/if}
+							</div>
+							{#if (item.value as FormCalendarValue)?.dates && (item.value as FormCalendarValue).dates!.length > 0}
+								<button
+									type="button"
+									class="shrink-0"
+									aria-label="clear"
+									onclick={(e) => {
+										e.stopPropagation();
+										item.value = { dates: [] };
+										handleFormChange(item, { dates: [] });
+									}}
+								>
+									<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" class="fill-current opacity-30">
+										<path
+											d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"
+										></path>
+									</svg>
+								</button>
+							{/if}
+							<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="shrink-0 fill-current opacity-50">
+								<path d="M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"
+								></path>
+							</svg>
+						</div>
 					</div>
-					<div class="flex space-x-2 text-xs"></div>
-				</div>
-				<Radio
-					data={item.radio?.data || []}
-					{...item.radio}
+				{:else}
+					<!-- 单选/范围模式:使用 Input 组件 -->
+					<Input
+						title={item.label}
+						select
+						value={(() => {
+							if (calendarDates.length === 0) return '';
+							const isRange = item.calendar?.mode === 'range';
+							if (isRange && calendarDates.length >= 2) {
+								return `${calendarDates[0]} - ${calendarDates[1]}`;
+							}
+							return calendarDates.join(',');
+						})()}
+						clear
+						required={item.required}
+						onfocus={() => {
+							item.showPopup = true;
+						}}
+						onclear={() => {
+							item.value = { dates: [] };
+							handleFormChange(item, { dates: [] });
+						}}
+						{...item.input}
+					/>
+				{/if}
+				{#key item.calendar?.mode === 'multiple' ? ((item.value as FormCalendarValue)?.dates || []).join('|') : item.name}
+					<Calendar
+						bind:visible={item.showPopup}
+						{...item.calendar}
+						initSelectedDates={(item.value as FormCalendarValue)?.dates || []}
+						onconfirm={(dates) => {
+							item.value = { dates };
+							handleFormChange(item, { dates });
+						}}
+						clear={false}
+					/>
+				{/key}
+			{:else if item.type === 'numKeyboard'}
+				<Input
+					title={item.label}
+					select
+					bind:value={item.value as string}
+					clear
+					required={item.required}
+					onfocus={() => {
+						item.showPopup = true;
+					}}
+					onclear={() => {
+						item.value = '';
+						handleFormChange(item, '');
+					}}
+					{...item.input}
+				/>
+				<NumKeyboard
+					bind:visible={item.showPopup}
+					onclick={(key) => {
+						if (key === 'close' || key === 'done') {
+							item.showPopup = false;
+						} else if (key === 'delete') {
+							item.value = (item.value as string).slice(0, -1);
+						} else {
+							item.value = (item.value as string) + key;
+						}
+						handleFormChange(item, item.value);
+					}}
+					{...item.numKeyboard}
+				/>
+			{:else if item.type === 'fullKeyboard'}
+				<Input
+					title={item.label}
+					select
+					bind:value={item.value as string}
+					clear
+					required={item.required}
+					onfocus={() => {
+						item.showPopup = true;
+					}}
+					onclear={() => {
+						item.value = '';
+						handleFormChange(item, '');
+					}}
+					{...item.input}
+				/>
+				<FullKeyboard
+					bind:visible={item.showPopup}
 					bind:value={item.value as string}
-					onchange={(value: string) => handleFormChange(item, value)}
+					onclick={() => {
+						handleFormChange(item, item.value);
+					}}
+					onclose={() => {
+						handleFormChange(item, item.value);
+					}}
+					{...item.fullKeyboard}
+				/>
+			{:else if item.type === 'colorPicker'}
+				<Input
+					title={item.label}
+					select
+					value={formatColorPickerValue(item.value as FormColorPickerValue)}
+					clear
+					required={item.required}
+					onfocus={() => {
+						item.showPopup = true;
+					}}
+					onclear={() => {
+						item.value = undefined;
+						handleFormChange(item, undefined);
+					}}
+					{...item.input}
+				/>
+				<ColorPicker
+					bind:visible={item.showPopup}
+					bind:value={item.value as FormColorPickerValue}
+					onchange={() => {
+						handleFormChange(item, item.value);
+					}}
+					onclose={() => {
+						handleFormChange(item, item.value);
+					}}
+					{...item.colorPicker}
 				/>
-			</div>
-		{:else if item.type === 'slider'}
-			<div class="p-2">
-				<div class="mb-2 flex justify-between px-2">
-					<div class="relative mb-1 text-sm font-semibold">
-						{#if item.required}
-							<span class="text-error absolute -left-2.5 text-base">*</span>
-						{/if}
-						{item.label}
+			{:else if item.type === 'picker'}
+				{@const pickerValue = item.value as FormPickerValue}
+				{@const pickerMultipleSelected = pickerValue?.multipleSelected || []}
+				{@const isMultiple = item.picker?.multiple}
+				{@const multiplePlaceholder =
+					item.input?.placeholder && item.input.placeholder !== ''
+						? item.input.placeholder
+						: item.label
+							? inputLang.pleaseSelect + ' ' + item.label
+							: ''}
+				{#if isMultiple}
+					<!-- 多选模式:使用自定义展示容器 -->
+					<div class="px-2 py-2">
+						<div class="flex justify-between px-2">
+							<div class="relative mb-1 text-sm font-semibold">
+								{#if item.required}
+									<span class="text-error absolute -left-2.5 text-base">*</span>
+								{/if}
+								{item.label}
+							</div>
+						</div>
+						<div
+							role="button"
+							tabindex="0"
+							class="bg-text-primary/5 dark:bg-text-dark/5 rounded-(--radius-form) relative my-0.5 flex min-h-12 w-full cursor-pointer items-center gap-2 px-2 py-3 text-left text-sm transition-all duration-300"
+							onclick={() => {
+								item.showPopup = true;
+							}}
+							onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && (item.showPopup = true)}
+						>
+							<div class="flex flex-1 flex-wrap items-center gap-2">
+								{#if (item.value as FormPickerValue)?.multipleSelected && (item.value as FormPickerValue).multipleSelected!.length > 0}
+									{#each (item.value as FormPickerValue).multipleSelected! as selectedItem, index (selectedItem.label)}
+										<Tag
+											text={selectedItem.label}
+											size="sm"
+											fill="light"
+											closable
+											onclose={() => {
+												const currentSelected = (item.value as FormPickerValue)?.multipleSelected || [];
+												const nextSelected = currentSelected.filter((_: unknown, i: number) => i !== index);
+												item.value = {
+													...(item.value as FormPickerValue),
+													multipleSelected: nextSelected
+												};
+												handleFormChange(item, item.value);
+											}}
+										/>
+									{/each}
+								{:else}
+									<span class="font-semibold opacity-50">{multiplePlaceholder}</span>
+								{/if}
+							</div>
+							{#if (item.value as FormPickerValue)?.multipleSelected && (item.value as FormPickerValue).multipleSelected!.length > 0}
+								<button
+									type="button"
+									class="shrink-0"
+									aria-label="clear"
+									onclick={(e) => {
+										e.stopPropagation();
+										item.value = {
+											items: [],
+											indexs: [],
+											multipleSelected: []
+										};
+										handleFormChange(item, item.value);
+									}}
+								>
+									<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" class="fill-current opacity-30">
+										<path
+											d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"
+										></path>
+									</svg>
+								</button>
+							{/if}
+							<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="shrink-0 fill-current opacity-50">
+								<path d="M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"
+								></path>
+							</svg>
+						</div>
 					</div>
-					<div class="flex space-x-2 text-xs"></div>
-				</div>
-				<div class="mx-4">
-					<Slider
-						{...item.slider}
-						onchange={(value, valueRange) => {
-							if (item.slider?.isRange) {
-								if (valueRange) {
-									(item.value as FormSliderValue).valueRange = valueRange;
-									handleFormChange(item, { valueRange });
-								}
-							} else {
-								(item.value as FormSliderValue).value = value;
-								handleFormChange(item, { value });
-							}
+				{:else}
+					<!-- 单选模式:使用 Input 组件 -->
+					<Input
+						title={item.label}
+						select
+						value={pickerValue?.items?.map((item) => item.label).join(' / ') || ''}
+						clear
+						required={item.required}
+						onfocus={() => {
+							item.showPopup = true;
 						}}
+						onclear={() => {
+							item.value = {
+								items: [],
+								indexs: [],
+								multipleSelected: undefined
+							};
+							handleFormChange(item, item.value);
+						}}
+						{...item.input}
+					/>
+				{/if}
+				<Picker
+					bind:visible={item.showPopup}
+					{...item.picker}
+					datas={item.picker?.datas || []}
+					multipleSelected={pickerMultipleSelected}
+					onmultiplechange={(selected) => {
+						item.value = {
+							...pickerValue,
+							multipleSelected: selected
+						};
+						handleFormChange(item, item.value);
+					}}
+					onconfirm={(items, indexs) => {
+						item.value = {
+							...pickerValue,
+							items,
+							indexs
+						};
+						handleFormChange(item, item.value);
+					}}
+					onclose={() => {
+						item.showPopup = false;
+					}}
+				/>
+			{:else if item.type === 'checkbox'}
+				<div class="p-2">
+					<div class="mb-2 flex justify-between px-2">
+						<div class="relative mb-1 text-sm font-semibold">
+							{#if item.required}
+								<span class="text-error absolute -left-2.5 text-base">*</span>
+							{/if}
+							{item.label}
+						</div>
+						<div class="flex space-x-2 text-xs"></div>
+					</div>
+					<Checkbox
+						data={item.checkbox?.data || []}
+						{...item.checkbox}
+						bind:checkeds={item.value as string[]}
+						onchange={(checkeds: string[]) => handleFormChange(item, checkeds)}
 					/>
 				</div>
-			</div>
-		{:else if item.type === 'switch'}
-			<div class="p-2">
-				<div class="mb-2 flex justify-between px-2">
-					<div class="relative mb-1 text-sm font-semibold">
-						{#if item.required}
-							<span class="text-error absolute -left-2.5 text-base">*</span>
-						{/if}
-						{item.label}
+			{:else if item.type === 'radio'}
+				<div class="p-2">
+					<div class="mb-2 flex justify-between px-2">
+						<div class="relative mb-1 text-sm font-semibold">
+							{#if item.required}
+								<span class="text-error absolute -left-2.5 text-base">*</span>
+							{/if}
+							{item.label}
+						</div>
+						<div class="flex space-x-2 text-xs"></div>
 					</div>
-					<div class="flex space-x-2 text-xs"></div>
+					<Radio
+						data={item.radio?.data || []}
+						{...item.radio}
+						bind:value={item.value as string}
+						onchange={(value: string) => handleFormChange(item, value)}
+					/>
 				</div>
-				<Switch {...item.switch} bind:active={item.value as boolean} onchange={(value: boolean) => handleFormChange(item, value)} />
-			</div>
-		{:else if item.type === 'stepper'}
-			<div class="p-2">
-				<div class="mb-2 flex justify-between px-2">
-					<div class="relative mb-1 text-sm font-semibold">
-						{#if item.required}
-							<span class="text-error absolute -left-2.5 text-base">*</span>
-						{/if}
-						{item.label}
+			{:else if item.type === 'slider'}
+				<div class="p-2">
+					<div class="mb-2 flex justify-between px-2">
+						<div class="relative mb-1 text-sm font-semibold">
+							{#if item.required}
+								<span class="text-error absolute -left-2.5 text-base">*</span>
+							{/if}
+							{item.label}
+						</div>
+						<div class="flex space-x-2 text-xs"></div>
+					</div>
+					<div class="mx-4">
+						<Slider
+							{...item.slider}
+							onchange={(value, valueRange) => {
+								if (item.slider?.isRange) {
+									if (valueRange) {
+										(item.value as FormSliderValue).valueRange = valueRange;
+										handleFormChange(item, { valueRange });
+									}
+								} else {
+									(item.value as FormSliderValue).value = value;
+									handleFormChange(item, { value });
+								}
+							}}
+						/>
 					</div>
-					<div class="flex space-x-2 text-xs"></div>
 				</div>
-				<Stepper {...item.stepper} bind:value={item.value as number} onchange={(value: number) => handleFormChange(item, value)} />
-			</div>
-		{:else}{/if}
-	{/each}
-</div>
+			{:else if item.type === 'switch'}
+				<div class="p-2">
+					<div class="mb-2 flex justify-between px-2">
+						<div class="relative mb-1 text-sm font-semibold">
+							{#if item.required}
+								<span class="text-error absolute -left-2.5 text-base">*</span>
+							{/if}
+							{item.label}
+						</div>
+						<div class="flex space-x-2 text-xs"></div>
+					</div>
+					<Switch {...item.switch} bind:active={item.value as boolean} onchange={(value: boolean) => handleFormChange(item, value)} />
+				</div>
+			{:else if item.type === 'stepper'}
+				<div class="p-2">
+					<div class="mb-2 flex justify-between px-2">
+						<div class="relative mb-1 text-sm font-semibold">
+							{#if item.required}
+								<span class="text-error absolute -left-2.5 text-base">*</span>
+							{/if}
+							{item.label}
+						</div>
+						<div class="flex space-x-2 text-xs"></div>
+					</div>
+					<Stepper {...item.stepper} bind:value={item.value as number} onchange={(value: number) => handleFormChange(item, value)} />
+				</div>
+			{:else}{/if}
+		{/each}
+	</div>
+{/snippet}
+
+{#if card}
+	<Card {...card}>
+		{@render formContent()}
+	</Card>
+{:else}
+	{@render formContent()}
+{/if}
 {#if submitChildren}
 	{@render submitChildren?.()}
 {:else if submitText}
@@ -458,5 +698,5 @@
 {#if resetChildren}
 	{@render resetChildren?.()}
 {:else if resetText}
-	<Button fill="lineTheme" type="button" onclick={resetFunc} {...resetButton}>{resetText}</Button>
+	<Button fill="lineState" type="button" onclick={resetFunc} {...resetButton}>{resetText}</Button>
 {:else}{/if}

+ 330 - 0
packages/stdf/src/lib/components/fullKeyboard/FullKeyboard.svelte

@@ -0,0 +1,330 @@
+<script lang="ts">
+	import { getContext } from 'svelte';
+	import Popup from '../popup/Popup.svelte';
+	import { zh_CN, type LangProps } from '../../lang/index.js';
+	import type { FullKeyboardProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
+
+	// 当前语言
+	// current language
+	const currentLang = (getContext('STDF_lang') || zh_CN) as LangProps;
+	const commonLang: LangProps['common'] = currentLang.common;
+
+	let {
+		value = $bindable(''),
+		visible = $bindable(true),
+		type = 'button',
+		mode = 'full',
+		done = true,
+		doneText = commonLang.done,
+		doneDisabled = $bindable(false),
+		radius = '',
+		preview = false,
+		previewMask = false,
+		panelClass = '',
+		keyClass: keyClassProp = '',
+		doneClass = '',
+		popup = {},
+		onclick,
+		onopen,
+		onclose
+	}: FullKeyboardProps = $props();
+
+	// 是否使用弹出层,当 popup 为 null 时不使用
+	// Whether to use popup, when popup is null, do not use
+	const usePopup = $derived(popup !== null);
+
+	// 是否大写模式
+	// Whether uppercase mode
+	let isUpperCase = $state(false);
+
+	// 是否符号模式(用于 full 模式下切换字母/数字符号)
+	// Whether symbol mode (for switching between letters and numbers/symbols in full mode)
+	let isSymbolMode = $state(false);
+
+	// QWERTY 键盘布局
+	// QWERTY keyboard layout
+	const letterRow1 = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'];
+	const letterRow2 = ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'];
+	const letterRow3 = ['z', 'x', 'c', 'v', 'b', 'n', 'm'];
+
+	// 数字行
+	// Number row
+	const numberRow = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
+
+	// 符号布局(美式键盘符号顺序)
+	// Symbol layout (US keyboard symbol order)
+	const symbolRow1 = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
+	const symbolRow2 = ['`', '~', '!', '@', '#', '$', '%', '^', '&', '*'];
+	const symbolRow3 = ['(', ')', '-', '_', '=', '+', '[', ']', '{', '}'];
+	const symbolRow4 = ['\\', '|', ';', ':', "'", '"', ',', '.', '<'];
+	const symbolRow5 = ['>', '/', '?'];
+
+	// 预览区高度(px)
+	// Preview area height (px)
+	const previewHeight = 44;
+
+	// 根据模式计算键盘高度
+	// Calculate keyboard height based on mode
+	const getKeyboardHeight = () => {
+		let baseHeight = 260;
+		if (mode === 'letterNumber' || mode === 'full') {
+			baseHeight = 304; // 多一行(数字或符号)
+		}
+		return preview ? baseHeight + previewHeight : baseHeight;
+	};
+
+	// 按键样式
+	// Key style
+	const keyClass = $derived(
+		type === 'button'
+			? `flex items-center justify-center h-11 bg-bg-highlight dark:bg-bg-highlight-dark shadow-xs font-medium active:scale-95 transition-all duration-100 text-lg text-text-primary dark:text-text-dark${keyClassProp ? ' ' + keyClassProp : ''}`
+			: `flex items-center justify-center h-11 bg-bg-highlight dark:bg-bg-highlight-dark font-medium active:opacity-40 transition-all duration-100 text-lg text-text-primary dark:text-text-dark${keyClassProp ? ' ' + keyClassProp : ''}`
+	);
+
+	// 功能键样式
+	// Function key style
+	const funcKeyClass = $derived(
+		type === 'button'
+			? `flex items-center justify-center h-11 bg-black/10 dark:bg-white/10 shadow-xs font-medium active:scale-95 transition-all duration-100${keyClassProp ? ' ' + keyClassProp : ''}`
+			: `flex items-center justify-center h-11 bg-black/10 dark:bg-white/10 font-medium active:opacity-40 transition-all duration-100${keyClassProp ? ' ' + keyClassProp : ''}`
+	);
+
+	// 获取按键圆角(block 模式下不使用圆角)
+	// Get key border radius (no radius in block mode)
+	const keyRadius = $derived(type === 'button' ? (radius ? radiusObj[radius] : 'rounded-(--radius-form)') : '');
+
+	// 间距 class
+	// Gap class
+	const gapClass = $derived(type === 'button' ? 'gap-0.5' : 'gap-px');
+
+	// 点击按键事件
+	// Click key event
+	const clickKey = (key: string) => {
+		const actualKey = isUpperCase ? key.toUpperCase() : key;
+		value += actualKey;
+		onclick?.(actualKey);
+	};
+
+	// 点击删除
+	// Click delete
+	const clickDelete = () => {
+		value = value.slice(0, -1);
+		onclick?.('delete');
+	};
+
+	// 点击空格
+	// Click space
+	const clickSpace = () => {
+		value += ' ';
+		onclick?.(' ');
+	};
+
+	// 点击完成
+	// Click done
+	const clickDone = () => {
+		if (!doneDisabled) {
+			visible = false;
+			onclick?.('done');
+		}
+	};
+
+	// 切换大小写
+	// Toggle uppercase
+	const toggleCase = () => {
+		isUpperCase = !isUpperCase;
+	};
+
+	// 切换符号模式
+	// Toggle symbol mode
+	const toggleSymbolMode = () => {
+		isSymbolMode = !isSymbolMode;
+	};
+
+	// 激活与关闭键盘事件
+	// Activate and close the keyboard event
+	$effect(() => {
+		if (visible) {
+			onopen?.(getKeyboardHeight());
+		} else {
+			onclose?.();
+		}
+	});
+</script>
+
+{#snippet shiftKey()}
+	<button
+		class="{funcKeyClass} {keyRadius} w-12 {isUpperCase ? 'bg-primary/20 dark:bg-dark/20' : ''}"
+		onclick={toggleCase}
+		aria-label="shift"
+	>
+		<svg width="18" height="16" viewBox="0 0 14 12" xmlns="http://www.w3.org/2000/svg" class="fill-current">
+			<path d="M4.64062 11.3613C3.70898 11.3613 3.1875 10.8105 3.1875 9.90234V7.46484H1.00195C0.416016 7.46484 0 7.11914 0 6.58008C0 6.24609 0.134766 6.04102 0.451172 5.71875L5.71875 0.392578C5.97656 0.140625 6.25781 0 6.55664 0C6.85547 0 7.13672 0.140625 7.39453 0.392578L12.6621 5.71875C12.9727 6.0293 13.1133 6.24609 13.1133 6.58008C13.1133 7.11914 12.6973 7.46484 12.1113 7.46484H9.93164V9.90234C9.93164 10.8105 9.4043 11.3613 8.47266 11.3613H4.64062ZM5.08594 9.82031H8.02734C8.20312 9.82031 8.33789 9.68555 8.33789 9.50977V6.31641C8.33789 6.18164 8.38477 6.12891 8.52539 6.12891H10.8398C10.8691 6.12891 10.8867 6.11133 10.8867 6.09375C10.8867 6.07617 10.8809 6.06445 10.8574 6.04102L6.64453 1.82227C6.61523 1.79297 6.58594 1.78125 6.55664 1.78125C6.52734 1.78125 6.49805 1.79297 6.47461 1.82227L2.25586 6.04102C2.23242 6.06445 2.22656 6.07617 2.22656 6.09375C2.22656 6.11133 2.24414 6.12891 2.2793 6.12891H4.58789C4.72852 6.12891 4.78125 6.18164 4.78125 6.31641V9.50977C4.78125 9.69141 4.9043 9.82031 5.08594 9.82031Z" />
+		</svg>
+	</button>
+{/snippet}
+
+{#snippet deleteKey()}
+	<button class="{funcKeyClass} {keyRadius} w-12" onclick={clickDelete} aria-label="delete">
+		<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" class="fill-current">
+			<path d="M6.53451 3H20.9993C21.5516 3 21.9993 3.44772 21.9993 4V20C21.9993 20.5523 21.5516 21 20.9993 21H6.53451C6.20015 21 5.88792 20.8329 5.70246 20.5547L0.369122 12.5547C0.145189 12.2188 0.145189 11.7812 0.369122 11.4453L5.70246 3.4453C5.88792 3.1671 6.20015 3 6.53451 3ZM7.06969 5L2.40302 12L7.06969 19H19.9993V5H7.06969ZM12.9993 10.5858L15.8277 7.75736L17.242 9.17157L14.4135 12L17.242 14.8284L15.8277 16.2426L12.9993 13.4142L10.1709 16.2426L8.75668 14.8284L11.5851 12L8.75668 9.17157L10.1709 7.75736L12.9993 10.5858Z" />
+		</svg>
+	</button>
+{/snippet}
+
+{#snippet letterRows()}
+	<div class="grid grid-cols-10 {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+		{#each letterRow1 as key (key)}
+			<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>
+				{isUpperCase ? key.toUpperCase() : key}
+			</button>
+		{/each}
+	</div>
+	<div class="grid grid-cols-9 {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'} px-4">
+		{#each letterRow2 as key (key)}
+			<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>
+				{isUpperCase ? key.toUpperCase() : key}
+			</button>
+		{/each}
+	</div>
+	<div class="flex {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+		{@render shiftKey()}
+		<div class="flex-1 grid grid-cols-7 {gapClass}">
+			{#each letterRow3 as key (key)}
+				<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>
+					{isUpperCase ? key.toUpperCase() : key}
+				</button>
+			{/each}
+		</div>
+		{@render deleteKey()}
+	</div>
+{/snippet}
+
+{#snippet keyboardContent()}
+	<div class="bg-black/5 dark:bg-white/5 select-none {type === 'button' ? 'p-1.5' : 'border-t border-gray-100 dark:border-gray-950'}{panelClass ? ' ' + panelClass : ''}">
+		{#if preview}
+			<!-- 输入预览区 -->
+			<!-- Input preview area -->
+			<div class="flex items-center justify-center h-11 {type === 'button' ? 'mb-1' : 'mb-px'} bg-bg-highlight dark:bg-bg-highlight-dark rounded-(--radius-form) text-xl font-semibold tracking-widest">
+				{#if previewMask && value}
+					{#each value.split('') as _ (_)}
+						<span class="w-2.5 h-2.5 mx-1 rounded-full bg-text-primary dark:bg-text-dark"></span>
+					{/each}
+				{:else}
+					{value || ''}
+				{/if}
+			</div>
+		{/if}
+
+		{#if mode === 'letter'}
+			<!-- 纯字母模式 -->
+			<!-- Letter only mode -->
+			{@render letterRows()}
+			<!-- 底部行:空格、完成 -->
+			<div class="flex {gapClass}">
+				<button class="{keyClass} {keyRadius} flex-1" onclick={clickSpace}>
+					<span class="text-sm opacity-60">{currentLang.fullKeyboard?.space || 'space'}</span>
+				</button>
+				{#if done}
+					<button
+						class="{keyRadius} w-24 h-11 bg-primary dark:bg-dark text-text-on-primary dark:text-text-on-dark font-medium {type === 'button' ? 'active:scale-95' : 'active:opacity-40'} transition-all duration-100 {doneDisabled ? 'opacity-50 active:scale-100' : ''}{doneClass ? ' ' + doneClass : ''}"
+						onclick={clickDone}
+					>
+						{doneText}
+					</button>
+				{/if}
+			</div>
+
+		{:else if mode === 'letterNumber'}
+			<!-- 字母+数字模式:数字在字母上方一行 -->
+			<!-- Letter + Number mode: numbers in a row above letters -->
+			<div class="grid grid-cols-10 {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+				{#each numberRow as key (key)}
+					<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>{key}</button>
+				{/each}
+			</div>
+			{@render letterRows()}
+			<!-- 底部行:空格、完成 -->
+			<div class="flex {gapClass}">
+				<button class="{keyClass} {keyRadius} flex-1" onclick={clickSpace}>
+					<span class="text-sm opacity-60">{currentLang.fullKeyboard?.space || 'space'}</span>
+				</button>
+				{#if done}
+					<button
+						class="{keyRadius} w-24 h-11 bg-primary dark:bg-dark text-text-on-primary dark:text-text-on-dark font-medium {type === 'button' ? 'active:scale-95' : 'active:opacity-40'} transition-all duration-100 {doneDisabled ? 'opacity-50 active:scale-100' : ''}{doneClass ? ' ' + doneClass : ''}"
+						onclick={clickDone}
+					>
+						{doneText}
+					</button>
+				{/if}
+			</div>
+
+		{:else}
+			<!-- full 模式:字母+数字+符号,可切换 -->
+			<!-- Full mode: letters + numbers + symbols, switchable -->
+			{#if isSymbolMode}
+				<!-- 数字符号模式 -->
+				<!-- Number symbol mode -->
+				<div class="grid grid-cols-10 {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+					{#each symbolRow1 as key (key)}
+						<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>{key}</button>
+					{/each}
+				</div>
+				<div class="grid grid-cols-10 {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+					{#each symbolRow2 as key (key)}
+						<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>{key}</button>
+					{/each}
+				</div>
+				<div class="grid grid-cols-10 {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+					{#each symbolRow3 as key (key)}
+						<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>{key}</button>
+					{/each}
+				</div>
+				<div class="flex {gapClass} {type === 'button' ? 'mb-0.5' : 'mb-px'}">
+					<div class="flex-1 grid grid-cols-9 {gapClass}">
+						{#each symbolRow4 as key (key)}
+							<button class="{keyClass} {keyRadius}" onclick={() => clickKey(key)}>{key}</button>
+						{/each}
+					</div>
+					{@render deleteKey()}
+				</div>
+			{:else}
+				<!-- 字母模式 -->
+				<!-- Letter mode -->
+				{@render letterRows()}
+			{/if}
+
+			<!-- 底部行:123/ABC、<>/? (符号模式)、空格、完成 -->
+			<!-- Bottom row: 123/ABC, <>/? (symbol mode), space, done -->
+			<div class="flex {gapClass}">
+				<button class="{funcKeyClass} {keyRadius} w-16 text-base" onclick={toggleSymbolMode}>
+					{isSymbolMode ? 'ABC' : '123'}
+				</button>
+				{#if isSymbolMode}
+					{#each symbolRow5 as key (key)}
+						<button class="{keyClass} {keyRadius} w-10" onclick={() => clickKey(key)}>{key}</button>
+					{/each}
+				{/if}
+				<button class="{keyClass} {keyRadius} flex-1" onclick={clickSpace}>
+					<span class="text-sm opacity-60">{currentLang.fullKeyboard?.space || 'space'}</span>
+				</button>
+				{#if done}
+					<button
+						class="{keyRadius} w-24 h-11 bg-primary dark:bg-dark text-text-on-primary dark:text-text-on-dark font-medium {type === 'button' ? 'active:scale-95' : 'active:opacity-40'} transition-all duration-100 {doneDisabled ? 'opacity-50 active:scale-100' : ''}{doneClass ? ' ' + doneClass : ''}"
+						onclick={clickDone}
+					>
+						{doneText}
+					</button>
+				{/if}
+			</div>
+		{/if}
+	</div>
+{/snippet}
+
+{#if usePopup}
+	<Popup bind:visible size={0} mask={{ opacity: '0' }} transitionDistance={getKeyboardHeight()} {...popup} radius={type === 'block' ? 'none' : popup?.radius}>
+		{@render keyboardContent()}
+	</Popup>
+{:else}
+	{@render keyboardContent()}
+{/if}

+ 15 - 2
packages/stdf/src/lib/components/icon/Icon.svelte

@@ -8,6 +8,7 @@
 		size = 24,
 		width = 0,
 		height = 0,
+		state,
 		theme = false,
 		opacity = 1,
 		path = getContext('STDF-global-icon-svg-path') || 'fonts/symbol.svg',
@@ -15,13 +16,25 @@
 		injClass = '',
 		children
 	}: IconProps = $props();
+
+	// 状态颜色映射
+	// state color mapping
+	const stateColorClass: Record<string, string> = {
+		theme: 'text-primary dark:text-dark',
+		success: 'text-success',
+		warning: 'text-warning',
+		error: 'text-error',
+		info: 'text-info'
+	};
+
+	const stateClass = $derived(state ? stateColorClass[state] || '' : theme ? stateColorClass.theme : '');
 </script>
 
 {#if children}
 	{@render children()}
 {:else if type === 'iconify' || type === 'iconify-color'}
 	<span
-		class="{type} {name} relative{theme ? ' text-primary dark:text-dark' : ''} {injClass}"
+		class="{type} {name} relative {stateClass} {injClass}"
 		style="opacity:{opacity}; width:{width || size}px; height:{height || size}px; top:{y}px;"
 	>
 	</span>
@@ -29,7 +42,7 @@
 	<svg
 		width={width || size}
 		height={height || size}
-		class="relative{theme ? ' text-primary dark:text-dark' : ''} inline fill-current {injClass}"
+		class="relative {stateClass} inline fill-current {injClass}"
 		style="fill-opacity:{opacity}; top:{y}px;"
 	>
 		<use xlink:href="/{path}#{name}" />

+ 260 - 0
packages/stdf/src/lib/components/imageList/ImageList.svelte

@@ -0,0 +1,260 @@
+<script lang="ts">
+	import Icon from '../icon/Icon.svelte';
+	import Loading from '../loading/Loading.svelte';
+	import ImagePreview from '../imagePreview/ImagePreview.svelte';
+	import type { ImageListProps, ImageListItemProps, LoadingProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
+
+	let {
+		value = $bindable([]),
+		columns = 4,
+		max = 9,
+		maxSize = 10,
+		disabled = false,
+		readonly = false,
+		deletable = true,
+		previewable = true,
+		gap = '2',
+		radius = '',
+		aspectRatio = [1, 1],
+		uploadText = '',
+		loading = {},
+		icon = {},
+		deleteIcon = {},
+		injClass = '',
+		itemInjClass = '',
+		uploadChild,
+		itemChild,
+		statusChild,
+		onadd,
+		ondelete,
+		onretry,
+		onpreview,
+		onexceed,
+		onoversized,
+		onclickUpload
+	}: ImageListProps = $props();
+
+	// 预览相关状态
+	// Preview related states
+	let previewVisible = $state(false);
+	let previewIndex = $state(0);
+
+	// 间距映射
+	// Gap mapping
+	const gapObj: Record<string, string> = {
+		'0': 'gap-0',
+		'1': 'gap-1',
+		'2': 'gap-2',
+		'3': 'gap-3',
+		'4': 'gap-4'
+	};
+
+	// 列数映射
+	// Columns mapping
+	const columnsObj: Record<number, string> = {
+		3: 'grid-cols-3',
+		4: 'grid-cols-4',
+		5: 'grid-cols-5'
+	};
+
+	// 圆角类名
+	// Radius class
+	const radiusClass = $derived(radius ? radiusObj[radius] : 'rounded-(--radius-btn)');
+
+	// 宽高比样式
+	// Aspect ratio style
+	const aspectRatioStyle = $derived(`aspect-ratio: ${aspectRatio[0]} / ${aspectRatio[1]}`);
+
+	// 是否显示上传按钮
+	// Show upload button
+	const showUploadBtn = $derived(!readonly && value.length < max);
+
+	// 合并默认图标参数
+	// Merge default icon parameters
+	const mergedIcon = $derived({
+		name: 'ri-add-line',
+		size: 28,
+		...icon
+	});
+
+	// 合并默认删除图标参数
+	// Merge default delete icon parameters
+	const mergedDeleteIcon = $derived({
+		name: 'ri-close-line',
+		size: 14,
+		...deleteIcon
+	});
+
+	// 合并默认加载参数
+	// Merge default loading parameters
+	const mergedLoading = $derived<LoadingProps>({
+		height: '8',
+		width: '8',
+		inverse: true,
+		...loading
+	});
+
+	// 生成唯一 ID
+	// Generate unique ID
+	const generateId = () => {
+		return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
+	};
+
+	// 点击上传按钮
+	// Click upload button
+	const handleUploadClick = () => {
+		if (disabled || readonly) return;
+		onclickUpload?.();
+	};
+
+	// 添加文件(供外部调用)
+	// Add files (for external use)
+	export function addFiles(files: File[]) {
+		if (disabled || readonly || files.length === 0) return;
+
+		// 检查数量限制
+		// Check count limit
+		const remainingSlots = max - value.length;
+		let filesToAdd = [...files];
+		if (filesToAdd.length > remainingSlots) {
+			onexceed?.(filesToAdd, max);
+			// 只取允许数量的文件
+			// Only take allowed number of files
+			filesToAdd = filesToAdd.slice(0, remainingSlots);
+		}
+
+		// 检查文件大小
+		// Check file size
+		const validFiles: File[] = [];
+		for (const file of filesToAdd) {
+			const sizeMB = file.size / (1024 * 1024);
+			if (sizeMB > maxSize) {
+				onoversized?.(file, maxSize);
+			} else {
+				validFiles.push(file);
+			}
+		}
+
+		if (validFiles.length === 0) return;
+
+		// 创建新的上传项
+		// Create new upload items
+		const newItems: ImageListItemProps[] = validFiles.map((file) => ({
+			id: generateId(),
+			file,
+			url: URL.createObjectURL(file),
+			status: 'pending' as const
+		}));
+
+		value = [...value, ...newItems];
+		onadd?.(validFiles);
+	}
+
+	// 删除图片
+	// Delete image
+	const handleDelete = (item: ImageListItemProps, index: number) => {
+		if (disabled || readonly) return;
+		// 释放 URL
+		// Revoke URL
+		if (item.url && item.file) {
+			URL.revokeObjectURL(item.url);
+		}
+		value = value.filter((_, i) => i !== index);
+		ondelete?.(item, index);
+	};
+
+	// 重试上传
+	// Retry upload
+	const handleRetry = (item: ImageListItemProps, index: number) => {
+		if (disabled || readonly) return;
+		onretry?.(item, index);
+	};
+
+	// 预览图片
+	// Preview image
+	const handlePreview = (item: ImageListItemProps, index: number) => {
+		if (!previewable) return;
+		previewIndex = index;
+		previewVisible = true;
+		onpreview?.(item, index);
+	};
+
+	// 获取预览图片列表
+	// Get preview image list
+	const previewImages = $derived(value.filter((item) => item.url).map((item) => ({ url: item.url || '', alt: item.message })));
+</script>
+
+<!-- 网格容器 Grid container -->
+<div class="grid {columnsObj[columns]} {gapObj[gap]} {injClass}">
+	<!-- 已上传图片列表 Uploaded images list -->
+	{#each value as item, index (item.id)}
+		<div class="relative overflow-hidden {radiusClass} {itemInjClass}" style={aspectRatioStyle}>
+			{#if itemChild}
+				{@render itemChild(item, index)}
+			{:else}
+				<!-- 图片 Image -->
+				{#if item.url}
+					<button class="h-full w-full" onclick={() => handlePreview(item, index)} {disabled}>
+						<img src={item.thumbnail || item.url} alt={item.message || ''} class="h-full w-full object-cover" />
+					</button>
+				{/if}
+
+				<!-- 状态遮罩 Status overlay -->
+				{#if item.status && item.status !== 'success'}
+					<div class="absolute inset-0 flex flex-col items-center justify-center bg-black/50">
+						{#if statusChild}
+							{@render statusChild(item)}
+						{:else if item.status === 'uploading'}
+							<!-- 上传中 Uploading -->
+							<Loading {...mergedLoading} />
+						{:else if item.status === 'error'}
+							<!-- 上传失败 Upload error -->
+							<button class="flex flex-col items-center text-white" onclick={() => handleRetry(item, index)}>
+								<Icon name="ri-refresh-line" size={24} />
+								<span class="mt-1 text-xs">{item.message || '重试'}</span>
+							</button>
+						{:else if item.status === 'pending'}
+							<!-- 等待上传 Pending -->
+							<span class="text-xs text-white">等待上传</span>
+						{/if}
+					</div>
+				{/if}
+
+				<!-- 删除按钮 Delete button -->
+				{#if deletable && !disabled && !readonly}
+					<button
+						class="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white"
+						onclick={() => handleDelete(item, index)}
+					>
+						<Icon {...mergedDeleteIcon} />
+					</button>
+				{/if}
+			{/if}
+		</div>
+	{/each}
+
+	<!-- 上传按钮 Upload button -->
+	{#if showUploadBtn}
+		<button
+			class="flex flex-col items-center justify-center bg-black/5 dark:bg-white/5 {radiusClass} {disabled
+				? 'cursor-not-allowed opacity-50'
+				: 'active:bg-black/10 dark:active:bg-white/10'}"
+			style={aspectRatioStyle}
+			onclick={handleUploadClick}
+			{disabled}
+		>
+			{#if uploadChild}
+				{@render uploadChild()}
+			{:else}
+				<Icon {...mergedIcon} opacity={0.5} />
+				{#if uploadText}
+					<span class="mt-1 text-xs text-black/50 dark:text-white/50">{uploadText}</span>
+				{/if}
+			{/if}
+		</button>
+	{/if}
+</div>
+
+<!-- 图片预览 Image preview -->
+<ImagePreview bind:visible={previewVisible} images={previewImages} bind:current={previewIndex} />

+ 565 - 0
packages/stdf/src/lib/components/imagePreview/ImagePreview.svelte

@@ -0,0 +1,565 @@
+<script lang="ts">
+	import { fade, scale } from 'svelte/transition';
+	import Mask from '../mask/Mask.svelte';
+	import Icon from '../icon/Icon.svelte';
+	import Loading from '../loading/Loading.svelte';
+	import type { ImagePreviewProps, ImagePreviewItemProps } from '../../types/index.js';
+
+	let {
+		visible = $bindable(false),
+		images = [],
+		current = $bindable(0),
+		loop = true,
+		swipeDuration = 300,
+		minScale = 0.5,
+		maxScale = 3,
+		closePosition = 'tr',
+		showNavigation = true,
+		navigationPosition = 'center',
+		maskClosable = false,
+		showIndex = true,
+		indicatorType = 'number',
+		zIndex = 1000,
+		duration = 300,
+		outDuration = 200,
+		mask = {},
+		icon = {},
+		showRotation = false,
+		rotationIcon = {},
+		children,
+		loadingChild,
+		errorChild,
+		indexChild,
+		onchange,
+		onclose,
+		onscale,
+		onrotate
+	}: ImagePreviewProps = $props();
+
+	// 标准化图片数据
+	// Normalize image data
+	const normalizedImages = $derived<ImagePreviewItemProps[]>(
+		images.map((img) => (typeof img === 'string' ? { url: img } : img))
+	);
+
+	// 图片总数
+	// Total images
+	const total = $derived(normalizedImages.length);
+
+	// 当前图片
+	// Current image
+	const currentImage = $derived(normalizedImages[current] || { url: '' });
+
+	// 图片加载状态 { [index]: 'loading' | 'loaded' | 'error' }
+	// Image load status
+	let loadStatus = $state<Record<number, 'loading' | 'loaded' | 'error'>>({});
+
+	// 图片旋转状态(内部使用,支持 360 用于连续动画)
+	// Image rotation status (internal use, supports 360 for continuous animation)
+	let rotationStatus = $state<Record<number, number>>({});
+
+	// 是否正在重置旋转(用于禁用动画)
+	// Whether resetting rotation (for disabling animation)
+	let isResettingRotation = $state(false);
+
+	// 缩放相关状态
+	// Scale related states
+	let currentScale = $state(1);
+	let translateX = $state(0);
+	let translateY = $state(0);
+
+	// Pointer 事件相关状态
+	// Pointer event related states
+	let activePointers = $state<Map<number, PointerEvent>>(new Map());
+	let startX = $state(0);
+	let startY = $state(0);
+	let isMoving = $state(false);
+	let isSwiping = $state(false);
+	let swipeStartX = $state(0);
+	let swipeOffset = $state(0);
+	let hasMoved = $state(false); // 是否有移动,用于判断点击 Whether moved, for tap detection
+
+	// 双指缩放状态
+	// Pinch zoom states
+	let isPinching = $state(false);
+	let pinchStartDistance = $state(0);
+	let pinchStartScale = $state(1);
+
+	// 容器元素引用
+	// Container element reference
+	let containerRef = $state<HTMLElement | null>(null);
+
+	// 关闭预览
+	// Close preview
+	const close = () => {
+		visible = false;
+		resetTransform();
+		onclose?.();
+	};
+
+	// 重置变换
+	// Reset transform
+	const resetTransform = () => {
+		currentScale = 1;
+		translateX = 0;
+		translateY = 0;
+	};
+
+	// 旋转当前图片(逆时针)
+	// Rotate current image (counterclockwise)
+	const rotate = () => {
+		const currentRotation = rotationStatus[current] || 0;
+		const newRotation = currentRotation + 90;
+		rotationStatus[current] = newRotation;
+
+		// 回调返回规范化的角度 (0, 90, 180, 270)
+		// Callback returns normalized angle (0, 90, 180, 270)
+		const normalizedAngle = (newRotation % 360) as 0 | 90 | 180 | 270;
+		onrotate?.(normalizedAngle);
+
+		// 当旋转到 360° 时,动画完成后重置为 0(无动画)
+		// When rotated to 360°, reset to 0 after animation completes (no animation)
+		if (newRotation % 360 === 0) {
+			const idx = current;
+			setTimeout(() => {
+				// 先禁用动画
+				// First disable animation
+				isResettingRotation = true;
+				// 等待 transition: none 生效后再更新角度
+				// Wait for transition: none to take effect before updating angle
+				requestAnimationFrame(() => {
+					requestAnimationFrame(() => {
+						rotationStatus[idx] = 0;
+						// 等待角度更新完成后恢复动画
+						// Restore animation after angle update completes
+						setTimeout(() => {
+							isResettingRotation = false;
+						}, 20);
+					});
+				});
+			}, 200);
+		}
+	};
+
+	// 切换图片
+	// Switch image
+	const switchImage = (index: number) => {
+		if (index === current) return;
+		let newIndex = index;
+		if (loop) {
+			if (index < 0) newIndex = total - 1;
+			else if (index >= total) newIndex = 0;
+		} else {
+			if (index < 0) newIndex = 0;
+			else if (index >= total) newIndex = total - 1;
+		}
+		if (newIndex !== current) {
+			current = newIndex;
+			resetTransform();
+			onchange?.(newIndex);
+		}
+	};
+
+	// 上一张
+	// Previous
+	const prev = () => switchImage(current - 1);
+
+	// 下一张
+	// Next
+	const next = () => switchImage(current + 1);
+
+	// 计算两点距离
+	// Calculate distance between two points
+	const getDistanceFromPointers = (p1: PointerEvent, p2: PointerEvent) => {
+		const dx = p1.clientX - p2.clientX;
+		const dy = p1.clientY - p2.clientY;
+		return Math.sqrt(dx * dx + dy * dy);
+	};
+
+	// Pointer 按下
+	// Pointer down
+	const handlePointerDown = (e: PointerEvent) => {
+		// 捕获 pointer
+		// Capture pointer
+		containerRef?.setPointerCapture(e.pointerId);
+		activePointers.set(e.pointerId, e);
+		hasMoved = false;
+
+		if (activePointers.size === 2) {
+			// 双指缩放开始
+			// Pinch zoom start
+			isPinching = true;
+			isSwiping = false;
+			isMoving = false;
+			const pointers = Array.from(activePointers.values());
+			pinchStartDistance = getDistanceFromPointers(pointers[0], pointers[1]);
+			pinchStartScale = currentScale;
+		} else if (activePointers.size === 1) {
+			startX = e.clientX;
+			startY = e.clientY;
+
+			// 滑动检测
+			// Swipe detection
+			if (currentScale === 1) {
+				isSwiping = true;
+				swipeStartX = e.clientX;
+				swipeOffset = 0;
+			} else {
+				isMoving = true;
+			}
+		}
+	};
+
+	// Pointer 移动
+	// Pointer move
+	const handlePointerMove = (e: PointerEvent) => {
+		if (!activePointers.has(e.pointerId)) return;
+
+		// 更新当前 pointer 位置
+		// Update current pointer position
+		activePointers.set(e.pointerId, e);
+
+		if (activePointers.size === 2 && isPinching) {
+			// 双指缩放
+			// Pinch zoom
+			hasMoved = true;
+			const pointers = Array.from(activePointers.values());
+			const currentDistance = getDistanceFromPointers(pointers[0], pointers[1]);
+			const scaleDelta = currentDistance / pinchStartDistance;
+			let newScale = pinchStartScale * scaleDelta;
+			newScale = Math.max(minScale, Math.min(maxScale, newScale));
+			currentScale = newScale;
+			onscale?.(newScale);
+		} else if (activePointers.size === 1) {
+			const deltaX = e.clientX - startX;
+			const deltaY = e.clientY - startY;
+
+			// 判断是否有明显移动
+			if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
+				hasMoved = true;
+			}
+
+			if (isSwiping && currentScale === 1) {
+				const swipeDeltaX = e.clientX - swipeStartX;
+				// 左右滑动切换
+				// Swipe to switch
+				swipeOffset = swipeDeltaX;
+				e.preventDefault();
+			} else if (isMoving && currentScale > 1) {
+				// 拖动图片
+				// Drag image
+				translateX += deltaX;
+				translateY += deltaY;
+				startX = e.clientX;
+				startY = e.clientY;
+				e.preventDefault();
+			}
+		}
+	};
+
+	// Pointer 抬起/取消
+	// Pointer up/cancel
+	const handlePointerUp = (e: PointerEvent) => {
+		containerRef?.releasePointerCapture(e.pointerId);
+		activePointers.delete(e.pointerId);
+
+		// 如果还有一个 pointer,重置为单指状态
+		// If one pointer left, reset to single finger state
+		if (activePointers.size === 1) {
+			isPinching = false;
+			const pointer = Array.from(activePointers.values())[0];
+			startX = pointer.clientX;
+			startY = pointer.clientY;
+			if (currentScale === 1) {
+				isSwiping = true;
+				swipeStartX = pointer.clientX;
+			} else {
+				isMoving = true;
+			}
+			return;
+		}
+
+		// 所有 pointer 都抬起
+		// All pointers up
+		if (activePointers.size === 0) {
+			isPinching = false;
+			isMoving = false;
+
+			// 滑动切换判断
+			// Swipe switch judgment
+			if (isSwiping) {
+				isSwiping = false;
+				const threshold = window.innerWidth * 0.2;
+				if (swipeOffset > threshold) {
+					prev();
+				} else if (swipeOffset < -threshold) {
+					next();
+				}
+				swipeOffset = 0;
+			}
+
+			// 点击关闭判断(没有移动 = 点击)
+			// Tap to close judgment (no movement = tap)
+			if (!hasMoved && maskClosable && currentScale === 1) {
+				close();
+				return;
+			}
+
+			// 缩放边界处理
+			// Scale boundary handling
+			if (currentScale < 1) {
+				currentScale = 1;
+				translateX = 0;
+				translateY = 0;
+			}
+
+		}
+	};
+
+	// 图片加载完成
+	// Image loaded
+	const handleImageLoad = (index: number) => {
+		loadStatus[index] = 'loaded';
+	};
+
+	// 图片加载失败
+	// Image load error
+	const handleImageError = (index: number) => {
+		loadStatus[index] = 'error';
+	};
+
+	// 当 visible 变化时重置状态并控制页面滚动
+	// Reset state when visible changes and control page scroll
+	$effect(() => {
+		if (visible) {
+			resetTransform();
+			// 禁止页面滚动
+			// Disable page scroll
+			document.body.style.overflow = 'hidden';
+		} else {
+			// 恢复页面滚动
+			// Restore page scroll
+			document.body.style.overflow = '';
+		}
+		return () => {
+			// 组件销毁时恢复页面滚动
+			// Restore page scroll when component is destroyed
+			document.body.style.overflow = '';
+		};
+	});
+
+	// 关闭按钮位置类名
+	// Close button position class
+	const closePositionClass = $derived(
+		{
+			tl: 'top-4 left-4',
+			tr: 'top-4 right-4',
+			bl: 'bottom-4 left-4',
+			br: 'bottom-4 right-4'
+		}[closePosition] || 'top-4 right-4'
+	);
+
+	// 合并默认图标参数
+	// Merge default icon parameters
+	const mergedIcon = $derived({
+		name: 'ri-close-line',
+		size: 24,
+		...icon
+	});
+
+	// 合并旋转图标参数
+	// Merge rotation icon parameters
+	const mergedRotationIcon = $derived({
+		size: 24,
+		...rotationIcon
+	});
+
+	// 当前图片的旋转角度
+	// Current image rotation
+	const currentRotation = $derived(rotationStatus[current] || 0);
+</script>
+
+{#if visible}
+	<!-- 遮罩层 Mask layer -->
+	<Mask
+		visible
+		opacity="0.9"
+		{duration}
+		{outDuration}
+		{...mask}
+		{zIndex}
+	/>
+
+	<!-- 主容器 Main container -->
+	<div
+		class="fixed inset-0 flex items-center justify-center"
+		style="z-index: {zIndex + 1}"
+		in:fade={{ duration }}
+		out:fade={{ duration: outDuration }}
+	>
+		<!-- 图片容器 Image container -->
+		<div
+			bind:this={containerRef}
+			class="relative w-full h-full overflow-hidden touch-none"
+			onpointerdown={handlePointerDown}
+			onpointermove={handlePointerMove}
+			onpointerup={handlePointerUp}
+			onpointercancel={handlePointerUp}
+		>
+			<!-- 图片滑动容器 Image slide container -->
+			<div
+				class="flex h-full transition-transform"
+				style="transform: translateX(calc({-current * 100}% + {swipeOffset}px)); transition-duration: {isSwiping ? '0ms' : `${swipeDuration}ms`}"
+			>
+				{#each normalizedImages as img, index (index)}
+					<div class="shrink-0 w-full h-full flex items-center justify-center">
+						{#if loadStatus[index] !== 'loaded' && loadStatus[index] !== 'error'}
+							<!-- 加载中 Loading -->
+							<div class="absolute inset-0 flex items-center justify-center">
+								{#if loadingChild}
+									{@render loadingChild()}
+								{:else}
+									<Loading height="12" width="12" theme />
+								{/if}
+							</div>
+						{/if}
+
+						{#if loadStatus[index] === 'error'}
+							<!-- 加载失败 Load error -->
+							<div class="flex flex-col items-center justify-center text-white/60">
+								{#if errorChild}
+									{@render errorChild()}
+								{:else}
+									<Icon name="ri-image-line" size={48} />
+									<span class="mt-2 text-sm">加载失败</span>
+								{/if}
+							</div>
+						{:else}
+							<!-- 图片 Image -->
+							<img
+								src={img.url}
+								alt={img.alt || ''}
+								class="max-w-full max-h-full object-contain select-none"
+								style={index === current
+									? `transform: scale(${currentScale}) translate(${translateX / currentScale}px, ${translateY / currentScale}px) rotate(-${rotationStatus[index] || 0}deg); transition: ${isMoving || isPinching || isResettingRotation ? 'none' : 'transform 0.2s'}`
+									: `transform: rotate(-${rotationStatus[index] || 0}deg); transition: ${isResettingRotation ? 'none' : 'transform 0.2s'}`}
+								draggable="false"
+								onload={() => handleImageLoad(index)}
+								onerror={() => handleImageError(index)}
+							/>
+						{/if}
+					</div>
+				{/each}
+			</div>
+
+			<!-- 自定义内容插槽 Custom content slot -->
+			{#if children}
+				<div class="absolute bottom-20 left-0 right-0 px-4">
+					{@render children(currentImage, current)}
+				</div>
+			{/if}
+		</div>
+
+		<!-- 关闭按钮和旋转按钮 Close button and rotation button -->
+		<div class="absolute {closePositionClass} flex gap-2" in:scale={{ duration: 200, delay: 100 }}>
+			{#if showRotation}
+				<!-- 旋转按钮 Rotation button -->
+				<button
+					class="p-2 rounded-full bg-black/30 text-white active:bg-black/50"
+					onclick={rotate}
+					aria-label="Rotate"
+				>
+					<!-- 逆时针旋转图标 Counterclockwise rotation icon -->
+					<svg xmlns="http://www.w3.org/2000/svg" width={mergedRotationIcon.size} height={mergedRotationIcon.size} viewBox="0 0 24 24" fill="currentColor">
+						<path d="M11 9H21C21.5522 9 22 9.44772 22 10V20C22 20.5523 21.5522 21 21 21H11C10.4477 21 9.99996 20.5523 9.99996 20V10C9.99996 9.44772 10.4477 9 11 9ZM12 11V19H20V11H12ZM5.99996 10.5858L7.82839 8.75736L9.24261 10.1716L4.99996 14.4142L0.757324 10.1716L2.17154 8.75736L3.99996 10.5858V8C3.99996 5.23858 6.23854 3 8.99996 3H13V5H8.99996C7.34311 5 5.99996 6.34315 5.99996 8V10.5858Z"></path>
+					</svg>
+				</button>
+			{/if}
+			<!-- 关闭按钮 Close button -->
+			<button
+				class="p-2 rounded-full bg-black/30 text-white active:bg-black/50"
+				onclick={close}
+			>
+				<Icon {...mergedIcon} />
+			</button>
+		</div>
+
+		<!-- 左右导航按钮(居中模式)Prev/Next navigation buttons (center mode) -->
+		{#if showNavigation && navigationPosition === 'center' && total > 1}
+			<!-- 上一张 Previous -->
+			{#if loop || current > 0}
+				<button
+					class="absolute left-4 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/30 text-white active:bg-black/50"
+					onclick={prev}
+					aria-label="Previous"
+					in:scale={{ duration: 200, delay: 100 }}
+				>
+					<Icon name="ri-arrow-left-s-line" size={24} />
+				</button>
+			{/if}
+			<!-- 下一张 Next -->
+			{#if loop || current < total - 1}
+				<button
+					class="absolute right-4 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/30 text-white active:bg-black/50"
+					onclick={next}
+					aria-label="Next"
+					in:scale={{ duration: 200, delay: 100 }}
+				>
+					<Icon name="ri-arrow-right-s-line" size={24} />
+				</button>
+			{/if}
+		{/if}
+
+		<!-- 底部栏:导航(底部模式)+ 索引指示器 Bottom bar: navigation (bottom mode) + index indicator -->
+		{#if (showIndex && total > 1) || (showNavigation && navigationPosition === 'bottom' && total > 1)}
+			<div class="absolute bottom-8 left-0 right-0 flex items-center justify-center gap-4 px-4">
+				<!-- 左侧导航(底部模式)Left navigation (bottom mode) -->
+				{#if showNavigation && navigationPosition === 'bottom' && total > 1 && (loop || current > 0)}
+					<button
+						class="p-2 rounded-full bg-black/30 text-white active:bg-black/50"
+						onclick={prev}
+						aria-label="Previous"
+						in:scale={{ duration: 200, delay: 100 }}
+					>
+						<Icon name="ri-arrow-left-s-line" size={24} />
+					</button>
+				{/if}
+
+				<!-- 索引指示器 Index indicator -->
+				{#if showIndex && total > 1}
+					{#if indexChild}
+						{@render indexChild(current + 1, total)}
+					{:else if indicatorType === 'number'}
+						<span class="px-3 py-1 rounded-full bg-black/30 text-white text-sm">
+							{current + 1} / {total}
+						</span>
+					{:else if indicatorType === 'dot'}
+						<div class="flex gap-2">
+							{#each normalizedImages as _, index (index)}
+								<button
+									class="w-2 h-2 rounded-full transition-all {index === current
+										? 'bg-white w-4'
+										: 'bg-white/50'}"
+									onclick={() => switchImage(index)}
+									aria-label="Go to image {index + 1}"
+								></button>
+							{/each}
+						</div>
+					{/if}
+				{/if}
+
+				<!-- 右侧导航(底部模式)Right navigation (bottom mode) -->
+				{#if showNavigation && navigationPosition === 'bottom' && total > 1 && (loop || current < total - 1)}
+					<button
+						class="p-2 rounded-full bg-black/30 text-white active:bg-black/50"
+						onclick={next}
+						aria-label="Next"
+						in:scale={{ duration: 200, delay: 100 }}
+					>
+						<Icon name="ri-arrow-right-s-line" size={24} />
+					</button>
+				{/if}
+			</div>
+		{/if}
+	</div>
+{/if}

+ 15 - 18
packages/stdf/src/lib/components/indexBar/IndexBar.svelte

@@ -1,7 +1,7 @@
-<script lang="ts">
+<script lang="ts" generics="T = string">
 	import { onMount } from 'svelte';
 	import { fly } from 'svelte/transition';
-	import { throttleWithRAF } from '../utils/index.js';
+	import { throttleWithRAF, radiusObj } from '../utils/index.js';
 	import type { IndexBarProps } from '../../types/index.js';
 
 	let {
@@ -9,12 +9,13 @@
 		current = $bindable(0),
 		top = 0,
 		height = 100,
-		radius = 'middle',
+		radius = '',
 		scrollAlign = true,
 		titleInjClass = '',
 		textInjClass = '',
+		children,
 		onclickChild
-	}: IndexBarProps = $props();
+	}: IndexBarProps<T> = $props();
 
 	// 用于绑定 bar 的高度
 	// Used to bind the height of the bar
@@ -46,10 +47,6 @@
 	// Height of each
 	let itemHeight = $derived(barHeight / data.length);
 
-	// 圆角风格样式
-	// Rounded style style
-	const radiusObj = { none: 'rounded-none', middle: 'rounded-sm', full: 'rounded-full' };
-
 	onMount(() => {
 		if (bodyDom) {
 			bodyDom.scrollTop = 0;
@@ -136,8 +133,12 @@
 		<div class="snap-start px-4 pt-8" bind:clientHeight={group.height}>
 			<div class="text-primary dark:text-dark text-sm {titleInjClass}">{group.title}</div>
 			{#each group.child as child, childIndex (childIndex)}
-				<button class="w-full py-2 text-left {textInjClass}" onclick={() => onclickChild && onclickChild(index, group, childIndex, child)}>
-					{child}
+				<button class="w-full py-2 text-left {textInjClass}" onclick={() => onclickChild?.(index, group, childIndex, child)}>
+					{#if children}
+						{@render children(child, childIndex, group, index)}
+					{:else}
+						{child}
+					{/if}
 				</button>
 				<div class="h-px bg-black/5 dark:bg-white/5"></div>
 			{/each}
@@ -150,25 +151,21 @@
 	onpointerup={touchBoxEnd}
 	bind:clientHeight={barHeight}
 	bind:this={barDom}
-	class="fixed right-5 flex w-7 cursor-move touch-none select-none flex-col justify-around bg-black/5 p-1 dark:bg-white/5 {radiusObj[
-		radius
-	] || radiusObj.middle}"
+	class="fixed right-5 flex w-7 cursor-move touch-none select-none flex-col justify-around bg-black/5 p-1 dark:bg-white/5 {radius ? radiusObj[radius] : 'rounded-(--radius-small)'}"
 	style="top:{top + (height - barHeight) / 2}px;min-height:{height / 4}px;"
 >
 	{#each data as group, i (i)}
 		<div class="relative flex flex-1 flex-col justify-center">
 			<div
 				class="h-5 w-5 text-center text-xs leading-5 transition-all {current === i
-					? 'bg-primary dark:bg-dark text-white dark:text-black'
-					: 'text-gray-600 dark:text-gray-400'} {radiusObj[radius] || radiusObj.middle}"
+					? 'bg-primary dark:bg-dark text-text-on-primary dark:text-text-on-dark'
+					: 'text-gray-600 dark:text-gray-400'} {radius ? radiusObj[radius] : 'rounded-(--radius-small)'}"
 			>
 				{group.index}
 			</div>
 			{#if currentTouch === i}
 				<div
-					class="border-primary text-primary dark:border-dark dark:text-dark absolute -left-24 top-1/2 h-14 w-14 -translate-y-2/4 border text-center text-3xl leading-[3.5rem] {radiusObj[
-						radius
-					] || radiusObj.middle}"
+					class="border-primary text-primary dark:border-dark dark:text-dark absolute -left-24 top-1/2 h-14 w-14 -translate-y-2/4 border text-center text-3xl leading-14 {radius ? radiusObj[radius] : 'rounded-(--radius-small)'}"
 					transition:fly={{ x: 38, duration: 300 }}
 				>
 					{group.index}

+ 95 - 25
packages/stdf/src/lib/components/input/Input.svelte

@@ -3,6 +3,7 @@
 	import Icon from '../icon/Icon.svelte';
 	import { zh_CN, type LangProps } from '../../lang/index.js';
 	import type { InputProps } from '../../types/index.js';
+	import { radiusObj } from '../utils/index.js';
 
 	// 当前语言
 	// current language
@@ -14,7 +15,7 @@
 		titlePosition = 'out',
 		inputPosition = 'left',
 		placeholder = '',
-		radius = 'sm',
+		radius = '',
 		label1 = null,
 		label2 = null,
 		label3 = null,
@@ -59,6 +60,7 @@
 		data1Child,
 		data2Child,
 		data3Child,
+		inputChild,
 		label1Child,
 		label2Child,
 		label3Child,
@@ -80,6 +82,10 @@
 	//textarea element
 	let textareaDom: HTMLTextAreaElement | undefined = $state(undefined);
 
+	// 输入框引用
+	// Input ref
+	let inputDom: HTMLInputElement | null = $state(null);
+
 	// 输入模式判断
 	// Input mode judgment
 	const modeFun = (type: string) => {
@@ -99,6 +105,17 @@
 	// Input box type judgment
 	const mode = $derived(inputmode === '' ? modeFun(type) : inputmode);
 
+	// 占位符文本
+	// Placeholder text
+	const placeholderText = $derived(
+		placeholder !== ''
+			? placeholder
+			: title !== ''
+				? (select ? inputLang.pleaseSelect : inputLang.pleaseInput) + ' ' + title
+				: ''
+	);
+	const displayMinHeight = $derived(type === 'textarea' ? `${rows * 1.5}rem` : '1.5rem');
+
 	// 输入框类型判断
 	// Input type judgment
 	const typeAction = (node: HTMLInputElement | HTMLTextAreaElement) => {
@@ -107,16 +124,18 @@
 		}
 	};
 
-	// 圆角风格样式
-	// Corner style style
-	const radiusObj = { none: 'rounded-none', sm: 'rounded-sm', xl: 'rounded-xl', '2xl': 'rounded-2xl', full: 'rounded-full' };
+	// 计算圆角类名
+	// Calculate radius class name
+	const radiusClass = $derived(
+		radius ? radiusObj[radius] : type === 'textarea' ? 'rounded-(--radius-box)' : 'rounded-(--radius-form)'
+	);
 
 	// 输入框风格样式
 	// Input box style style
-	const inputStyleObj = {
-		block: 'px-2 ring-2 ring-transparent bg-black/5 dark:bg-white/5 ' + radiusObj[radius] || radiusObj.sm,
-		line: 'px-1 border-b bg-transparent border-black/20 dark:border-white/20'
-	};
+	const inputStyleObj = $derived({
+		block: 'px-2 ring-2 ring-transparent bg-text-primary/5 dark:bg-text-dark/5 ' + radiusClass,
+		line: 'px-1 border-b bg-transparent border-text-primary/20 dark:border-text-dark/20'
+	});
 
 	// 状态样式
 	// State style
@@ -131,8 +150,8 @@
 	// 根据是否获取焦点判断输入框样式
 	// Determine the input box style according to whether to get focus
 	const inputStyleFocusObj = $derived({
-		block: 'px-2 ring-2 bg-transparent ' + stateObj[inputState] || stateObj.theme + radiusObj[radius] || radiusObj.sm,
-		line: 'px-1 border-b bg-transparent border-black/20 dark:border-white/20'
+		block: 'px-2 ring-2 bg-transparent ' + (stateObj[inputState] || stateObj.theme) + ' ' + radiusClass,
+		line: 'px-1 border-b bg-transparent border-text-primary/20 dark:border-text-dark/20'
 	});
 
 	// 线性动画样式
@@ -260,6 +279,15 @@
 		// Dispatch events and pass out the key of the key
 		onkeydown?.(e.key);
 	};
+
+	const focusInput = () => {
+		if (disabled) return;
+		if (type === 'textarea') {
+			textareaDom?.focus();
+		} else {
+			inputDom?.focus();
+		}
+	};
 </script>
 
 <div class="px-2 {pyObj[py] || pyObj['2']}">
@@ -291,10 +319,10 @@
 			</div>
 		</div>
 		<div
-			class="relative my-0.5 flex items-center space-x-1 whitespace-nowrap text-sm transition-all {durationObj[duration] ||
+			class="relative my-0.5 flex space-x-1 text-sm transition-all {inputChild ? 'items-start whitespace-normal' : 'items-center whitespace-nowrap'} {durationObj[duration] ||
 				durationObj.base} {titlePosition === 'in' ? 'py-1' : 'py-3'} {focus
 				? inputStyleFocusObj[inputStyle] || inputStyleFocusObj.block
-				: inputStyleObj[inputStyle] || inputStyleObj.block} {inputStyle === 'block' ? radiusObj[radius] || radiusObj.sm : ''}"
+				: inputStyleObj[inputStyle] || inputStyleObj.block} {inputStyle === 'block' ? radiusClass : ''}"
 		>
 			{#if label1Child}
 				{@render label1Child?.()}
@@ -328,18 +356,64 @@
 				{/if}
 				<div class="flex space-x-1">
 					<div class="w-full">
-						{#if type === 'textarea'}
+						{#if inputChild}
+							<div class="relative w-full">
+								{#if type === 'textarea'}
+									<textarea
+										bind:value
+										use:typeAction
+										{rows}
+										inputmode={mode as 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'email' | 'search' | 'url'}
+										placeholder={placeholderText}
+										class="focus:outline-hidden absolute inset-0 h-full w-full opacity-0 pointer-events-none"
+										onfocus={onFocus}
+										onblur={() => onBlur()}
+										autocomplete={autocomplete === true ? 'on' : 'off'}
+										{disabled}
+										readonly
+										tabindex="-1"
+										bind:this={textareaDom}
+										onkeydown={keydownFunc}
+									></textarea>
+								{:else}
+									<input
+										bind:value
+										use:typeAction
+										inputmode={mode as 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'email' | 'search' | 'url'}
+										placeholder={placeholderText}
+										class="focus:outline-hidden absolute inset-0 h-full w-full opacity-0 pointer-events-none"
+										onfocus={onFocus}
+										onblur={() => onBlur()}
+										autocomplete={autocomplete === true ? 'on' : 'off'}
+										{disabled}
+										readonly
+										tabindex="-1"
+										bind:this={inputDom}
+									/>
+								{/if}
+								<div
+									class="flex w-full flex-wrap items-center gap-2 text-text-primary dark:text-text-dark {inputPosition ===
+									'left'
+										? 'text-left'
+										: 'text-right'} {disabled ? 'cursor-not-allowed opacity-50' : 'cursor-text'}"
+									style="min-height: {displayMinHeight};"
+									role="textbox"
+									tabindex={disabled ? -1 : 0}
+									onfocus={focusInput}
+									onclick={focusInput}
+									onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && focusInput()}
+								>
+									{@render inputChild?.()}
+								</div>
+							</div>
+						{:else if type === 'textarea'}
 							<textarea
 								bind:value
 								use:typeAction
 								{rows}
 								inputmode={mode as 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'email' | 'search' | 'url'}
-								placeholder={placeholder !== ''
-									? placeholder
-									: title !== ''
-										? (select ? inputLang.pleaseSelect : inputLang.pleaseInput) + ' ' + title
-										: ''}
-								class="focus:outline-hidden w-full bg-transparent font-semibold text-black dark:text-white {inputPosition === 'left'
+								placeholder={placeholderText}
+								class="focus:outline-hidden w-full bg-transparent font-semibold text-text-primary dark:text-text-dark {inputPosition === 'left'
 									? 'text-left'
 									: 'text-right'} {disabled ? 'cursor-not-allowed opacity-50' : ''}"
 								onfocus={onFocus}
@@ -358,12 +432,8 @@
 								bind:value
 								use:typeAction
 								inputmode={mode as 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'email' | 'search' | 'url'}
-								placeholder={placeholder !== ''
-									? placeholder
-									: title !== ''
-										? (select ? inputLang.pleaseSelect : inputLang.pleaseInput) + ' ' + title
-										: ''}
-								class="focus:outline-hidden w-full whitespace-normal bg-transparent font-semibold text-black dark:text-white {inputPosition ===
+								placeholder={placeholderText}
+								class="focus:outline-hidden w-full whitespace-normal bg-transparent font-semibold text-text-primary dark:text-text-dark {inputPosition ===
 								'left'
 									? 'text-left'
 									: 'text-right'} {disabled ? 'cursor-not-allowed opacity-50' : ''}"

+ 683 - 0
packages/stdf/src/lib/components/list/List.svelte

@@ -0,0 +1,683 @@
+<script lang="ts" generics="T extends Record<string, unknown>">
+	import { getContext } from 'svelte';
+	import Icon from '../icon/Icon.svelte';
+	import { radiusObj } from '../utils/index.js';
+	import { zh_CN, type LangProps } from '../../lang/index.js';
+	import type { ListProps, SwipeActionProps } from '../../types/index.js';
+
+	// 当前语言
+	// Current language
+	const currentLang: LangProps = getContext('STDF_lang') || zh_CN;
+	const listLang = (currentLang.list || zh_CN.list) as NonNullable<LangProps['list']>;
+
+	let {
+		data = [],
+		keyField = 'id',
+		gap = '0',
+		mx = '0',
+		my = '0',
+		itemPx = '0',
+		itemPy = '0',
+		transition: listTransition = 'slideRight',
+		transitionDelay = 50,
+		batchMode = $bindable(false),
+		batchSelected = $bindable([]),
+		batchSelectable = false,
+		batchActions = [],
+		injClass = '',
+		itemInjClass = '',
+		arrow = false,
+		divider = true,
+		itemRadius = '',
+		// 滑动操作相关
+		// Swipe action related
+		swipeActions = [],
+		swipeHint = 'first',
+		swipeThreshold = 30,
+		// Snippets
+		itemChild,
+		headerChild,
+		footerChild,
+		// Events
+		onbatchChange,
+		onclickItem,
+		onswipeAction
+	}: ListProps<T> = $props();
+
+	// 滑动操作状态
+	// Swipe action state
+	let swipeStartX = 0;
+	let swipeStartY = 0;
+	let swipeMovedDistance = 0;
+	let swipeMovedKey: string | number | null = null;
+	let swipeClickBlockKey: string | number | null = null;
+	let swipeOffsets: Record<string | number, number> = $state({});
+	let activeSwipeKey: string | number | null = $state(null);
+	let isSwiping = false; // 是否正在滑动 Whether swiping
+
+	// 计算滑动操作项的限制(最多 4 项)
+	// Compute limited swipe actions (max 4)
+	const limitedSwipeActions = $derived(swipeActions.slice(0, 4));
+
+	// 是否有滑动操作
+	// Whether has swipe actions
+	const hasSwipeActions = $derived(limitedSwipeActions.length > 0);
+
+	// 滑动操作按钮总宽度
+	// Swipe action buttons total width
+	const swipeActionWidth = $derived(limitedSwipeActions.length * 64);
+
+	// 批量选择图标宽度
+	// Batch selection icon width
+	const batchSelectWidth = 40;
+
+	// 间距样式映射
+	// Gap style mapping
+	const gapClass: Record<string, string> = {
+		'0': 'gap-0',
+		'1': 'gap-1',
+		'2': 'gap-2',
+		'3': 'gap-3',
+		'4': 'gap-4',
+		'6': 'gap-6',
+		'8': 'gap-8'
+	};
+
+	const mxClass: Record<string, string> = {
+		'0': 'mx-0',
+		'1': 'mx-1',
+		'2': 'mx-2',
+		'3': 'mx-3',
+		'4': 'mx-4',
+		'6': 'mx-6',
+		'8': 'mx-8'
+	};
+
+	const myClass: Record<string, string> = {
+		'0': 'my-0',
+		'1': 'my-1',
+		'2': 'my-2',
+		'3': 'my-3',
+		'4': 'my-4',
+		'6': 'my-6',
+		'8': 'my-8'
+	};
+
+	const itemPxClass: Record<string, string> = {
+		'0': 'px-0',
+		'1': 'px-1',
+		'2': 'px-2',
+		'3': 'px-3',
+		'4': 'px-4',
+		'6': 'px-6',
+		'8': 'px-8'
+	};
+
+	const itemPyClass: Record<string, string> = {
+		'0': 'py-0',
+		'1': 'py-1',
+		'2': 'py-2',
+		'3': 'py-3',
+		'4': 'py-4',
+		'6': 'py-6',
+		'8': 'py-8'
+	};
+
+	// 背景色映射
+	// Background color mapping
+	const bgColorClass: Record<string, string> = {
+		theme: 'bg-primary dark:bg-dark',
+		success: 'bg-success',
+		warning: 'bg-warning',
+		error: 'bg-error',
+		info: 'bg-info'
+	};
+
+	// 文字颜色映射
+	// Text color mapping
+	const textColorClass: Record<string, string> = {
+		theme: 'text-primary dark:text-dark',
+		success: 'text-success',
+		warning: 'text-warning',
+		error: 'text-error',
+		info: 'text-info'
+	};
+
+	const itemRadiusClass = $derived(itemRadius ? radiusObj[itemRadius] : '');
+	const swipeActionRadiusClass = $derived(itemRadius ? `${radiusObj[itemRadius]} rounded-l-none` : '');
+
+	// 获取项目的 key
+	// Get item key
+	const getItemKey = (item: T, index: number): string | number => {
+		if (keyField && item[keyField] !== undefined) {
+			return item[keyField] as string | number;
+		}
+		return index;
+	};
+
+	type ListRenderItem = {
+		key: string | number;
+		item: T;
+		leaving: boolean;
+		leaveTimer?: ReturnType<typeof setTimeout>;
+	};
+
+	let renderItems = $state<ListRenderItem[]>([]);
+	const listTransitionDuration = 300;
+
+	$effect(() => {
+		const currentItems = renderItems;
+		const currentMap = new Map(currentItems.map((item) => [item.key, item]));
+		const nextItems: ListRenderItem[] = data.map((item, index) => {
+			const key = getItemKey(item, index);
+			const existing = currentMap.get(key);
+			if (existing) {
+				existing.item = item;
+				if (existing.leaving) {
+					existing.leaving = false;
+					if (existing.leaveTimer) {
+						clearTimeout(existing.leaveTimer);
+						existing.leaveTimer = undefined;
+					}
+				}
+				return existing;
+			}
+			return { key, item, leaving: false };
+		});
+		const nextKeySet = new Set(nextItems.map((item) => item.key));
+		const merged: ListRenderItem[] = [];
+		let nextIndex = 0;
+
+		for (const currentItem of currentItems) {
+			if (nextKeySet.has(currentItem.key)) {
+				while (nextIndex < nextItems.length && nextItems[nextIndex].key !== currentItem.key) {
+					merged.push(nextItems[nextIndex]);
+					nextIndex += 1;
+				}
+				if (nextIndex < nextItems.length) {
+					merged.push(nextItems[nextIndex]);
+					nextIndex += 1;
+				}
+			} else if (listTransition) {
+				if (!currentItem.leaving) {
+					currentItem.leaving = true;
+					const leavingKey = currentItem.key;
+					if (currentItem.leaveTimer) {
+						clearTimeout(currentItem.leaveTimer);
+					}
+					currentItem.leaveTimer = setTimeout(() => {
+						renderItems = renderItems.filter((item) => item.key !== leavingKey);
+					}, listTransitionDuration);
+				}
+				merged.push(currentItem);
+			}
+		}
+
+		while (nextIndex < nextItems.length) {
+			merged.push(nextItems[nextIndex]);
+			nextIndex += 1;
+		}
+
+		const isSame = merged.length === currentItems.length && merged.every((item, index) => item === currentItems[index]);
+		if (!isSame) {
+			renderItems = merged;
+		}
+	});
+
+	// 滑动操作指针事件
+	// Swipe action pointer events
+	const handleSwipeStart = (e: PointerEvent, itemKey: string | number) => {
+		if (!hasSwipeActions || batchMode) return;
+		isSwiping = true;
+		swipeMovedDistance = 0;
+		swipeMovedKey = null;
+		swipeStartX = e.clientX;
+		swipeStartY = e.clientY;
+		// 捕获指针以确保持续接收事件
+		// Capture pointer to ensure continuous event receiving
+		(e.target as HTMLElement).setPointerCapture(e.pointerId);
+		// 如果有其他项打开,先关闭
+		if (activeSwipeKey !== null && activeSwipeKey !== itemKey) {
+			closeSwipe(activeSwipeKey);
+		}
+	};
+
+	const handleSwipeMove = (e: PointerEvent, itemKey: string | number) => {
+		if (!hasSwipeActions || batchMode || !isSwiping) return;
+		const currentX = e.clientX;
+		const currentY = e.clientY;
+		const diffX = currentX - swipeStartX;
+		const diffY = currentY - swipeStartY;
+
+		// 如果垂直滑动大于水平滑动,不处理
+		if (Math.abs(diffY) > Math.abs(diffX)) return;
+
+		const currentOffset = swipeOffsets[itemKey] || 0;
+		let newOffset = currentOffset + diffX;
+
+		// 限制滑动范围
+		newOffset = Math.max(-swipeActionWidth, Math.min(0, newOffset));
+
+		swipeMovedDistance += Math.abs(diffX);
+		if (swipeMovedDistance > 4) {
+			swipeMovedKey = itemKey;
+		}
+
+		swipeOffsets = { ...swipeOffsets, [itemKey]: newOffset };
+		swipeStartX = currentX;
+	};
+
+	const handleSwipeEnd = (e: PointerEvent, itemKey: string | number) => {
+		if (!hasSwipeActions || batchMode || !isSwiping) return;
+		isSwiping = false;
+		// 释放指针捕获
+		// Release pointer capture
+		(e.target as HTMLElement).releasePointerCapture(e.pointerId);
+		const offset = swipeOffsets[itemKey] || 0;
+
+		// 根据滑动距离决定是否打开或关闭
+		if (Math.abs(offset) > swipeThreshold) {
+			// 打开滑动操作
+			swipeOffsets = { ...swipeOffsets, [itemKey]: -swipeActionWidth };
+			activeSwipeKey = itemKey;
+		} else {
+			// 关闭滑动操作
+			closeSwipe(itemKey);
+		}
+
+		if (swipeMovedKey === itemKey) {
+			swipeClickBlockKey = itemKey;
+			setTimeout(() => {
+				if (swipeClickBlockKey === itemKey) {
+					swipeClickBlockKey = null;
+				}
+			}, 0);
+		}
+		swipeMovedKey = null;
+	};
+
+	const closeSwipe = (itemKey: string | number) => {
+		swipeOffsets = { ...swipeOffsets, [itemKey]: 0 };
+		if (activeSwipeKey === itemKey) {
+			activeSwipeKey = null;
+		}
+	};
+
+	// 点击滑动操作按钮
+	// Click swipe action button
+	const handleSwipeActionClick = (actionIndex: number, action: SwipeActionProps, item: T, itemIndex: number, itemKey: string | number) => {
+		// 先关闭滑动
+		closeSwipe(itemKey);
+		// 触发操作的 onclick
+		action.onclick?.();
+		// 触发组件的 onswipeAction
+		onswipeAction?.(actionIndex, action, item, itemIndex);
+	};
+
+	// 批量选择处理
+	// Batch selection handling
+	const handleBatchSelect = (item: T, index: number) => {
+		const key = getItemKey(item, index);
+		if (batchSelected.includes(key)) {
+			batchSelected = batchSelected.filter((k) => k !== key);
+		} else {
+			batchSelected = [...batchSelected, key];
+		}
+		onbatchChange?.(batchSelected);
+	};
+
+	// 全选
+	// Select all
+	const handleSelectAll = () => {
+		if (batchSelected.length === data.length) {
+			batchSelected = [];
+		} else {
+			batchSelected = data.map((item, index) => getItemKey(item, index));
+		}
+		onbatchChange?.(batchSelected);
+	};
+
+	// 切换批量模式
+	// Toggle batch mode
+	const toggleBatchMode = () => {
+		batchMode = !batchMode;
+		if (!batchMode) {
+			batchSelected = [];
+			onbatchChange?.(batchSelected);
+		}
+	};
+
+	// 点击列表项
+	// Click list item
+	const handleItemClick = (item: T, index: number, itemKey: string | number) => {
+		if (swipeClickBlockKey === itemKey) return;
+		// 如果有滑动打开,先关闭
+		if (activeSwipeKey !== null) {
+			closeSwipe(activeSwipeKey);
+			return;
+		}
+		// 如果批量模式,触发选择
+		if (batchMode) {
+			handleBatchSelect(item, index);
+			return;
+		}
+		// 触发点击事件
+		if (onclickItem) {
+			onclickItem(item, index);
+		}
+	};
+
+</script>
+
+{#snippet swipeHintIcon()}
+	<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-3 w-3">
+		<path d="M10.071 4.92896L11.4852 6.34317L6.82834 11L16.0002 11.0002L16.0002 13.0002L6.82839 13L11.4852 17.6569L10.071 19.0711L2.99994 12L10.071 4.92896ZM18.0001 19V4.99997H20.0001V19H18.0001Z"></path>
+	</svg>
+{/snippet}
+
+{#snippet batchUncheckedIcon()}
+	<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 text-gray-400 dark:text-gray-500">
+		<path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20Z"></path>
+	</svg>
+{/snippet}
+
+{#snippet batchCheckedIcon()}
+	<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 text-primary dark:text-dark">
+		<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM17.4571 9.45711L11 15.9142L6.79289 11.7071L8.20711 10.2929L11 13.0858L16.0429 8.04289L17.4571 9.45711Z"></path>
+	</svg>
+{/snippet}
+
+{#snippet listItem(item: T, index: number, itemKey: string | number)}
+	{@const swipeOffset = swipeOffsets[itemKey] || 0}
+	{@const batchOffset = batchMode ? batchSelectWidth : 0}
+	{@const isSwipeActive = swipeOffset !== 0}
+
+	<div class="relative {hasSwipeActions ? 'overflow-x-clip' : ''}">
+		<!-- 批量选择图标层 -->
+		<!-- Batch selection icon layer -->
+		{#if batchMode}
+			<button
+				class="absolute inset-y-0 left-0 flex items-center justify-center bg-bg-surface dark:bg-bg-surface-dark"
+				style="width: {batchSelectWidth}px"
+				onclick={() => handleBatchSelect(item, index)}
+			>
+				{#if batchSelected.includes(itemKey)}
+					{@render batchCheckedIcon()}
+				{:else}
+					{@render batchUncheckedIcon()}
+				{/if}
+			</button>
+		{/if}
+
+		<!-- 滑动操作按钮层 -->
+		<!-- Swipe action buttons layer -->
+		{#if hasSwipeActions && !batchMode}
+			<div class="absolute inset-y-0 right-0 flex overflow-hidden {swipeActionRadiusClass} {isSwipeActive ? '' : 'opacity-0 pointer-events-none'}">
+				{#each limitedSwipeActions as action, actionIndex (actionIndex)}
+					{@const actionColor = action.bgColor === 'primary' ? 'theme' : action.bgColor || 'theme'}
+					<button
+						class="flex h-full w-16 flex-col items-center justify-center {bgColorClass[actionColor]} {actionColor === 'theme' ? 'text-text-on-primary dark:text-text-on-dark' : 'text-white'}"
+						onclick={() => handleSwipeActionClick(actionIndex, action, item, index, itemKey)}
+					>
+						{#if action.icon}
+							<Icon name={action.icon} size={20} />
+						{/if}
+						{#if action.text}
+							<span class="mt-1 text-xs">{action.text}</span>
+						{/if}
+					</button>
+				{/each}
+			</div>
+		{/if}
+
+		<!-- 列表项内容层 -->
+		<!-- List item content layer -->
+		<div
+			class="relative transition-transform duration-200 touch-none"
+			style="transform: translateX({swipeOffset + batchOffset}px)"
+			onpointerdown={(e) => handleSwipeStart(e, itemKey)}
+			onpointermove={(e) => handleSwipeMove(e, itemKey)}
+			onpointerup={(e) => handleSwipeEnd(e, itemKey)}
+			onpointercancel={(e) => handleSwipeEnd(e, itemKey)}
+		>
+			<button
+				class="flex w-full items-start gap-2 text-left {itemPxClass[itemPx] || 'px-0'} {itemPyClass[itemPy] || 'py-0'} {itemRadiusClass} {itemInjClass}"
+				onclick={() => handleItemClick(item, index, itemKey)}
+				disabled={!onclickItem && !batchMode}
+			>
+				<div class="min-w-0 flex-1">
+					{@render itemChild(item, index)}
+				</div>
+				{#if arrow && !batchMode}
+					<div class="shrink-0 self-center text-gray-400 dark:text-gray-500">
+						<Icon name="ri-arrow-right-s-line" size={20} />
+					</div>
+				{/if}
+			</button>
+			{#if hasSwipeActions && !batchMode && (swipeHint === 'all' || (swipeHint === 'first' && index === 0))}
+				<div class="absolute right-1 top-1/2 -translate-y-1/2 text-gray-300 dark:text-gray-600">
+					{@render swipeHintIcon()}
+				</div>
+			{/if}
+		</div>
+	</div>
+{/snippet}
+
+<div class="relative {mxClass[mx] || 'mx-0'} {myClass[my] || 'my-0'} {injClass}">
+	<!-- 批量操作栏 -->
+	<!-- Batch operation bar -->
+	{#if batchSelectable}
+		<div class="flex items-center justify-between px-4 py-2">
+			{#if batchMode}
+				<div class="flex items-center gap-4">
+					<button
+						class="text-primary dark:text-dark text-sm"
+						onclick={handleSelectAll}
+					>
+						{listLang.selectAllText} ({batchSelected.length}/{data.length})
+					</button>
+					{#if batchSelected.length > 0}
+						{#each batchActions as action, actionIndex (actionIndex)}
+							<button
+								class="text-sm {textColorClass[action.status || 'theme']}"
+								onclick={() => action.onclick?.(batchSelected)}
+							>
+								{action.text}
+							</button>
+						{/each}
+					{/if}
+				</div>
+				<button
+					class="text-primary dark:text-dark text-sm"
+					onclick={toggleBatchMode}
+				>
+					{listLang.doneText}
+				</button>
+			{:else}
+				<span></span>
+				<button
+					class="text-primary dark:text-dark text-sm"
+					onclick={toggleBatchMode}
+				>
+					{listLang.editText}
+				</button>
+			{/if}
+		</div>
+	{/if}
+
+	<!-- 头部插槽 -->
+	<!-- Header slot -->
+	{#if headerChild}
+		{@render headerChild()}
+	{/if}
+
+	<!-- 列表内容 -->
+	<!-- List content -->
+	<div class="flex flex-col {gapClass[gap] || 'gap-2'} {divider ? 'divide-y divide-black/10 dark:divide-white/10' : ''}">
+		{#each renderItems as renderItem, index (renderItem.key)}
+			{@const itemKey = renderItem.key}
+			<div
+				class="relative"
+				class:stdf-list-transition={!!listTransition && !renderItem.leaving}
+				class:stdf-list-slide-right={!renderItem.leaving && listTransition === 'slideRight'}
+				class:stdf-list-slide-up={!renderItem.leaving && listTransition === 'slideUp'}
+				class:stdf-list-fade-scale={!renderItem.leaving && listTransition === 'fadeScale'}
+				class:stdf-list-stagger={!renderItem.leaving && listTransition === 'stagger'}
+				class:stdf-list-leave={!!listTransition && renderItem.leaving}
+				class:stdf-list-leave-slide-right={renderItem.leaving && listTransition === 'slideRight'}
+				class:stdf-list-leave-slide-up={renderItem.leaving && listTransition === 'slideUp'}
+				class:stdf-list-leave-fade-scale={renderItem.leaving && listTransition === 'fadeScale'}
+				class:stdf-list-leave-stagger={renderItem.leaving && listTransition === 'stagger'}
+				style={listTransition
+					? `animation-delay: ${renderItem.leaving ? 0 : index * transitionDelay}ms; --stdf-list-stagger-x: ${
+							index % 2 === 0 ? '-100px' : '100px'
+						};`
+					: ''}
+			>
+				{@render listItem(renderItem.item, index, itemKey)}
+			</div>
+		{/each}
+	</div>
+
+	<!-- 底部插槽 -->
+	<!-- Footer slot -->
+	{#if footerChild}
+		{@render footerChild()}
+	{/if}
+</div>
+
+<style>
+	.stdf-list-transition {
+		animation-duration: 300ms;
+		animation-fill-mode: both;
+		animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
+		will-change: opacity, transform;
+	}
+
+	.stdf-list-leave {
+		animation-duration: 300ms;
+		animation-fill-mode: both;
+		animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
+		will-change: opacity, transform;
+	}
+
+	.stdf-list-slide-right {
+		animation-name: stdf-list-slide-right;
+	}
+
+	.stdf-list-slide-up {
+		animation-name: stdf-list-slide-up;
+	}
+
+	.stdf-list-fade-scale {
+		animation-name: stdf-list-fade-scale;
+	}
+
+	.stdf-list-stagger {
+		animation-name: stdf-list-stagger;
+	}
+
+	.stdf-list-leave-slide-right {
+		animation-name: stdf-list-slide-right-out;
+	}
+
+	.stdf-list-leave-slide-up {
+		animation-name: stdf-list-slide-up-out;
+	}
+
+	.stdf-list-leave-fade-scale {
+		animation-name: stdf-list-fade-scale-out;
+	}
+
+	.stdf-list-leave-stagger {
+		animation-name: stdf-list-stagger-out;
+	}
+
+	@keyframes stdf-list-slide-right {
+		from {
+			opacity: 0;
+			transform: translateX(100px);
+		}
+		to {
+			opacity: 1;
+			transform: translateX(0);
+		}
+	}
+
+	@keyframes stdf-list-slide-up {
+		from {
+			opacity: 0;
+			transform: translateY(50px);
+		}
+		to {
+			opacity: 1;
+			transform: translateY(0);
+		}
+	}
+
+	@keyframes stdf-list-fade-scale {
+		from {
+			opacity: 0;
+			transform: scale(0.8);
+		}
+		to {
+			opacity: 1;
+			transform: scale(1);
+		}
+	}
+
+	@keyframes stdf-list-stagger {
+		from {
+			opacity: 0;
+			transform: translateX(var(--stdf-list-stagger-x));
+		}
+		to {
+			opacity: 1;
+			transform: translateX(0);
+		}
+	}
+
+	@keyframes stdf-list-slide-right-out {
+		from {
+			opacity: 1;
+			transform: translateX(0);
+		}
+		to {
+			opacity: 0;
+			transform: translateX(100px);
+		}
+	}
+
+	@keyframes stdf-list-slide-up-out {
+		from {
+			opacity: 1;
+			transform: translateY(0);
+		}
+		to {
+			opacity: 0;
+			transform: translateY(50px);
+		}
+	}
+
+	@keyframes stdf-list-fade-scale-out {
+		from {
+			opacity: 1;
+			transform: scale(1);
+		}
+		to {
+			opacity: 0;
+			transform: scale(0.8);
+		}
+	}
+
+	@keyframes stdf-list-stagger-out {
+		from {
+			opacity: 1;
+			transform: translateX(0);
+		}
+		to {
+			opacity: 0;
+			transform: translateX(var(--stdf-list-stagger-x));
+		}
+	}
+</style>

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_10.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} m-auto flex items-center justify-between">
-	{#each [0, 1, 2] as i}
+	{#each [0, 1, 2] as i (i)}
 		<div
 			class="{colorClass()} loading h-2 w-2 rounded-full"
 			style="animation-delay: {i === 1 ? -0.3 : 0}s;background: {customColor[0]};animation-duration: {1 /

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_11.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [0, 1, 2, 3, 4] as i}
+	{#each [0, 1, 2, 3, 4] as i (i)}
 		<div
 			class="absolute -left-1/2 top-1/4{colorClass()} loading h-4 w-4 rounded-full opacity-60"
 			style="animation-delay: {-0.5 * i}s, {-0.5 * (i + 1)}s;top:calc(5);background: {customColor[0]};animation-duration: {2.5 /

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_16.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} m-auto flex items-center justify-between">
-	{#each [0, 1, 2] as i}
+	{#each [0, 1, 2] as i (i)}
 		<div
 			class="{colorClass()} loading h-2 w-2 rounded-full"
 			style="animation-delay: {i * 100 - 300}ms;background: {customColor[0]};animation-duration: {1 /

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_17.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} m-auto flex items-center justify-between">
-	{#each [0, 1, 2] as i}
+	{#each [0, 1, 2] as i (i)}
 		<div
 			class="{colorClass()} loading h-2 w-2 rounded-full"
 			style="animation-delay: {i * 100 - 300}ms;background: {customColor[0]};animation-duration: {1 /

+ 2 - 2
packages/stdf/src/lib/components/loading/loadings/oneColor/1_22.svelte

@@ -17,9 +17,9 @@
 		viewBox="0 0 40 40"
 		style="color: {customColor[0]}"
 	>
-		<circle class="track stroke-[5px] opacity-0" stroke="currentColor" cx="20" cy="20" r="17.5" fill="none" />
+		<circle class="track stroke-1.25 opacity-0" stroke="currentColor" cx="20" cy="20" r="17.5" fill="none" />
 		<circle
-			class="car stroke-[5px]"
+			class="car stroke-1.25"
 			style="animation-duration: {(2 / speed) * 0.75}s;-webkit-animation-duration: {(2 / speed) * 0.75}s;"
 			stroke="currentColor"
 			cx="20"

+ 2 - 2
packages/stdf/src/lib/components/loading/loadings/oneColor/1_23.svelte

@@ -12,9 +12,9 @@
 </script>
 
 <div class="{size} relative m-auto -translate-x-0.5">
-	{#each new Array(12) as item, i}
+	{#each new Array(12) as item, i (i)}
 		<div
-			class="absolute left-1/2 flex h-8 w-[3px] flex-col gap-5 rounded-full bg-transparent"
+			class="absolute left-1/2 flex h-8 w-0.75 flex-col gap-5 rounded-full bg-transparent"
 			style="transform: rotate(calc(360deg / -12*{i}));"
 		>
 			<div

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_27.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} container relative m-auto flex items-center" style="animation-duration: {2 / speed}s;">
-	{#each [0, -0.835, -0.668, -0.501, -0.334, -0.167] as item}
+	{#each [0, -0.835, -0.668, -0.501, -0.334, -0.167] as item (item)}
 		<div
 			class="dot absolute left-0 right-0 flex"
 			style="animation-duration: {2 / speed}s; animation-delay: calc({2 / speed}s * {item} * 0.5);"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_28.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto flex items-center">
-	{#each [0, -0.875, -0.75, -0.625, -0.5, -0.375, -0.25, -0.125] as item, i}
+	{#each [0, -0.875, -0.75, -0.625, -0.5, -0.375, -0.25, -0.125] as item, i (i)}
 		<div class="absolute left-0 right-0 flex" style="transform: rotate(calc(360deg / 8 * {i})); ">
 			<div
 				class="dot h-1.5 w-1.5 rounded-full{colorClass()}"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_29.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} container relative m-auto flex items-center">
-	{#each [0, -0.125, -0.25, -0.375, -0.5, -0.625, -0.75, -0.875] as item, i}
+	{#each [0, -0.125, -0.25, -0.375, -0.5, -0.625, -0.75, -0.875] as item, i (i)}
 		<div class="absolute left-0 right-0 flex" style="transform: rotate({i * 45}deg); animation-duration: {0.3 / speed}s">
 			<div
 				class="dot h-1.5 w-1.5 rounded-full{colorClass()}"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_30.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} m-auto flex items-end justify-between">
-	{#each [0, -0.36, -0.2] as item}
+	{#each [0, -0.36, -0.2] as item (item)}
 		<div class="cube h-2 w-2" style="animation-duration: {1.75 / speed}s; animation-delay: calc({1.75 / speed}s * {item})">
 			<div
 				class="cube__inner h-2 w-2 rounded-xs{colorClass()}"

+ 2 - 2
packages/stdf/src/lib/components/loading/loadings/oneColor/1_31.svelte

@@ -12,9 +12,9 @@
 </script>
 
 <div class="{size} m-auto flex items-center justify-between">
-	{#each [-0.45, -0.3, -0.15, 0] as item}
+	{#each [-0.45, -0.3, -0.15, 0] as item (item)}
 		<div
-			class="bar h-full w-[3px]{colorClass()}"
+			class="bar h-full w-0.75{colorClass()}"
 			style="background-color: {customColor[0]}; animation-duration: {1 / speed}s; animation-delay: {1 * item}s"
 		></div>
 	{/each}

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_39.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} m-auto flex flex-col items-center justify-center">
-	{#each [0, 1, 2, 3, 4, 5] as item}
+	{#each [0, 1, 2, 3, 4, 5] as item (item)}
 		<div class="relative w-full" style="height: calc(32px / 6);">
 			<div
 				class="slice absolute top-0 h-full rounded-full shrink-0{colorClass()}"

+ 2 - 2
packages/stdf/src/lib/components/loading/loadings/oneColor/1_4.svelte

@@ -19,11 +19,11 @@
 			speed}s;-webkit-animation-duration: {1 / speed}s;"
 	>
 		<div
-			class="absolute{colorClass()} border-l-[5px] border-r-[5px] border-t-[5px] border-l-transparent border-r-transparent dark:border-l-transparent dark:border-r-transparent"
+			class="absolute{colorClass()} border-l-1.25 border-r-1.25 border-t-1.25 border-l-transparent border-r-transparent dark:border-l-transparent dark:border-r-transparent"
 			style="transform: rotate(225deg);border-color: {customColor[0]};border-right-color:transparent;border-left-color:transparent;"
 		></div>
 		<div
-			class="absolute bottom-0 right-0{colorClass()} border-l-[5px] border-r-[5px] border-t-[5px] border-l-transparent border-r-transparent dark:border-l-transparent dark:border-r-transparent"
+			class="absolute bottom-0 right-0{colorClass()} border-l-1.25 border-r-1.25 border-t-1.25 border-l-transparent border-r-transparent dark:border-l-transparent dark:border-r-transparent"
 			style="transform: rotate(45deg);border-color: {customColor[0]};border-right-color:transparent;border-left-color:transparent;"
 		></div>
 	</div>

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_40.svelte

@@ -30,7 +30,7 @@
 </script>
 
 <div class="{size} relative m-auto flex items-center">
-	{#each [-0.48, -0.4, -0.32, -0.24, -0.4, -0.32, -0.24, -0.16, -0.32, -0.24, -0.16, -0.08, -0.24, -0.16, -0.08, -0] as item, i}
+	{#each [-0.48, -0.4, -0.32, -0.24, -0.4, -0.32, -0.24, -0.16, -0.32, -0.24, -0.16, -0.08, -0.24, -0.16, -0.08, -0] as item, i (i)}
 		<div
 			class="dot absolute h-1 w-1 rounded-full{colorClass()}"
 			style="background-color: {customColor[0]};bottom: calc({brList[i][0]}% + 1.6px);right: calc({brList[

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_42.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} container relative m-auto inline-block" style="animation-duration: {1.5 / speed}s;">
-	{#each [120, -120, 0] as item}
+	{#each [120, -120, 0] as item (item)}
 		<div class="absolute h-full" style="transform: rotate({item}deg);left: 37.5%;width:25%;">
 			<div
 				class="dot absolute left-0 top-0 h-0 w-full rounded-full{colorClass()}"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_43.svelte

@@ -13,7 +13,7 @@
 
 <div class="{size} m-auto flex flex-col justify-center">
 	<div class="flex items-center justify-between">
-		{#each [-0.45, -0.3, -0.15, 0] as item}
+		{#each [-0.45, -0.3, -0.15, 0] as item (item)}
 			<div
 				class="dot h-1.5 w-1.5 rounded-full{colorClass()}"
 				style="background-color: {customColor[0]};animation-duration: {1 / speed}s;animation-delay: {(1 / speed) * item}s"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_44.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [0, 0.4, 0.8] as item, i}
+	{#each [0, 0.4, 0.8] as item, i (i)}
 		<div
 			class="dot absolute left-0 top-0 flex h-full w-full items-center"
 			style="animation-duration: {2.5 / speed}s;transform: translateX({32 * item}px);animation-delay: {2.5 / speed / (-1.5 * i)}s"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_45.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} m-auto flex items-center justify-center">
-	{#each [0, 1, 2, 3] as item}
+	{#each [0, 1, 2, 3] as item (item)}
 		<div
 			class="dot relative flex h-full w-1/4 items-center"
 			class:dot1={item === 0}

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_46.svelte

@@ -13,7 +13,7 @@
 
 <div class="{size} m-auto flex flex-col justify-center">
 	<div class="relative flex h-1/5 w-full -translate-x-[10%] items-center justify-between">
-		{#each [0, -0.2, -0.4, -0.6, -0.8] as item}
+		{#each [0, -0.2, -0.4, -0.6, -0.8] as item (item)}
 			<div
 				class="dot absolute h-full w-1/5 rounded-full{colorClass()}"
 				style="background-color: {customColor[0]};animation-delay: {(2.5 / speed) * item}s;animation-duration: {2.5 / speed}s"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_47.svelte

@@ -13,7 +13,7 @@
 
 <div class="{size} m-auto flex flex-col justify-center">
 	<div class="relative flex items-center justify-between">
-		{#each [-0.375, -0.25, -0.125] as item}
+		{#each [-0.375, -0.25, -0.125] as item (item)}
 			<div
 				class="dot h-2 w-2 rounded-full{colorClass()}"
 				style="background-color: {customColor[0]};animation-duration: {1.3 / speed}s;animation-delay: {(1.3 / speed) * item}s"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_48.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto flex -rotate-90 items-center">
-	{#each [1, 0.9, 0.6, 0.4] as item, i}
+	{#each [1, 0.9, 0.6, 0.4] as item, i (i)}
 		<div
 			class="dot absolute left-0 flex h-full w-full flex-col items-center"
 			style="animation-duration: {1.6 / speed}s;animation-delay: {(1.6 / speed) * -(0.4 - i * 0.09)}s;"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_49.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [0, -2] as item}
+	{#each [0, -2] as item (item)}
 		<div
 			class="dot absolute left-0 top-0 h-full w-full rounded-full{colorClass()}"
 			style="background-color: {customColor[0]};animation-duration: {2 / speed}s;animation-delay: {2 / speed / item}s;"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_50.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [0, -2] as item}
+	{#each [0, -2] as item (item)}
 		<div
 			class="dot absolute left-0 top-0 h-full w-full rounded-full{colorClass()}"
 			style="background-color: {customColor[0]};animation-duration: {2 / speed}s;animation-delay: {2 / speed / item}s;"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_51.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [0, -0.25, -0.5, -0.75] as item}
+	{#each [0, -0.25, -0.5, -0.75] as item (item)}
 		<div
 			class="dot absolute left-0 top-0 h-full w-full rounded-full{colorClass()}"
 			style="background-color: {customColor[0]};animation-duration: {2 / speed}s;animation-delay: {(2 / speed) * item}s"

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_52.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [[-0.375, 1], [-0.375, 0.8], [-0.3, 0.6], [-0.225, 0.4], [-0.15, 0.2], [-0.075, 0.1]] as item}
+	{#each [[-0.375, 1], [-0.375, 0.8], [-0.3, 0.6], [-0.225, 0.4], [-0.15, 0.2], [-0.075, 0.1]] as item, i (i)}
 		<div
 			class="line absolute left-0 h-1 w-full rounded-full{colorClass()}"
 			style="background-color: {customColor[0]};top: calc(50% - 2px);animation-duration: {0.9 / speed}s;animation-delay: {(0.9 / speed) *

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_53.svelte

@@ -12,7 +12,7 @@
 </script>
 
 <div class="{size} relative m-auto">
-	{#each [[-0.375, 1], [-0.375, 0.8], [-0.3, 0.6], [-0.225, 0.4], [-0.15, 0.2], [-0.075, 0.1]] as item}
+	{#each [[-0.375, 1], [-0.375, 0.8], [-0.3, 0.6], [-0.225, 0.4], [-0.15, 0.2], [-0.075, 0.1]] as item, i (i)}
 		<div
 			class="line absolute left-0 h-1 w-full rounded-full{colorClass()}"
 			style="background-color: {customColor[0]};top: 50%;animation-duration: {0.9 / speed}s;animation-delay: {(0.9 / speed) *

+ 1 - 1
packages/stdf/src/lib/components/loading/loadings/oneColor/1_9.svelte

@@ -13,7 +13,7 @@
 </script>
 
 <div class="relative {size} m-auto">
-	{#each [0, 1, 2] as i}
+	{#each [0, 1, 2] as i (i)}
 		<div
 			class="absolute{colorClass()} loading h-1/3 w-1/3 rounded-full"
 			style="animation-delay: {i - 2}s;background: {customColor[0]};animation-duration: {1.5 / speed}s;-webkit-animation-duration: {1.5 /

Some files were not shown because too many files changed in this diff