Stepper.svelte 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <script>
  2. import { createEventDispatcher } from 'svelte';
  3. import Icon from '../icon/Icon.svelte';
  4. import Loading from '../loading/Loading.svelte';
  5. /**
  6. * 当前值
  7. * Current value
  8. * @type {number}
  9. * @range min - max
  10. * @default 0
  11. */
  12. export let value = 10;
  13. /**
  14. * 最小值
  15. * Minimum value
  16. * @type {number}
  17. * @default 0
  18. */
  19. export let min = 0;
  20. /**
  21. * 最大值
  22. * Maximum value
  23. * @type {number}
  24. * @default 100
  25. */
  26. export let max = 100;
  27. /**
  28. * 步长
  29. * Step length
  30. * @type {number}
  31. * @default 1
  32. */
  33. export let step = 1;
  34. /**
  35. * 是否纵向
  36. * Whether it is vertical
  37. * @type {boolean}
  38. * @default false
  39. */
  40. export let vertical = false;
  41. /**
  42. * 强调数字区域
  43. * Highlight the number area
  44. * @type {boolean}
  45. * @default false
  46. */
  47. export let numberHighlight = false;
  48. /**
  49. * 强调区域是否是主题色
  50. * Whether the highlight area is the theme color
  51. * @type {boolean}
  52. * @default true
  53. */
  54. export let theme = true;
  55. /**
  56. * 圆角风格
  57. * Round style
  58. * @type {'none'|'base'|'xl'|'full'}
  59. * @default 'base'
  60. */
  61. export let radius = 'base';
  62. /**
  63. * 对内部显示数字保留小数位数
  64. * The number of decimal places to display internally
  65. * @type {number}
  66. * @default 0
  67. */
  68. export let decimal = 0;
  69. /**
  70. * 是否异步状态
  71. * Whether it is in asynchronous state
  72. * @type {boolean}
  73. * @default false
  74. */
  75. export let async = false;
  76. /**
  77. * 异步状态时,是否显示内部loading
  78. * Whether to show internal loading when in asynchronous state
  79. * @type {boolean}
  80. * @default false
  81. */
  82. export let asyncLoading = false;
  83. /**
  84. * 异步状态时,loading的参数
  85. * Loading parameters when in asynchronous state
  86. * @type {object}
  87. * @default {}
  88. */
  89. export let loading = {};
  90. /**
  91. * 外部有无 padding
  92. * Whether there is external padding
  93. * @type {boolean}
  94. * @default true
  95. */
  96. export let padding = true;
  97. /**
  98. * 外部注入的类
  99. * External injected class
  100. * @type {string}
  101. * @default ''
  102. */
  103. export let injClassOut = '';
  104. /**
  105. * 按钮区域注入的类
  106. * Button area injected class
  107. * @type {string}
  108. * @default ''
  109. */
  110. export let injClassBtn = '';
  111. /**
  112. * 数字区域注入的类
  113. * Number area injected class
  114. * @type {string}
  115. * @default ''
  116. */
  117. export let injClassNum = '';
  118. // 圆角
  119. // Round
  120. const roundObj = {
  121. none: 'rounded-none',
  122. base: 'rounded',
  123. xl: 'rounded-xl',
  124. full: 'rounded-full',
  125. };
  126. const dispatch = createEventDispatcher();
  127. // 减少
  128. // Decrease
  129. const decreaseFn = () => {
  130. if (!async) {
  131. if (value > min) {
  132. value = Math.max(min, value - step);
  133. dispatch('change', value);
  134. }
  135. }
  136. dispatch('decrease');
  137. };
  138. // 增加
  139. // Increase
  140. const increaseFn = () => {
  141. if (!async) {
  142. if (value < max) {
  143. value = Math.min(max, value + step);
  144. dispatch('change', value);
  145. }
  146. }
  147. dispatch('increase');
  148. };
  149. </script>
  150. <div
  151. class="inline-flex items-center bg-gray-100 dark:bg-gray-800 {roundObj[radius] || 'rounded'} {padding
  152. ? 'p-1'
  153. : 'p-0'} overflow-hidden {injClassOut} {vertical ? 'flex-col-reverse' : 'flex-row'}"
  154. >
  155. <button
  156. on:click={decreaseFn}
  157. class="{vertical ? 'w-full' : 'w-9'} h-9 {roundObj[radius] ||
  158. 'rounded'} flex flex-col items-center {injClassBtn} justify-center{numberHighlight
  159. ? ''
  160. : theme
  161. ? ' bg-primary/5 dark:bg-dark/10'
  162. : ' bg-white dark:bg-black'}"
  163. disabled={async || value === min}
  164. aria-label="decrease"
  165. >
  166. <span class={async || value === min ? 'opacity-20' : ''}>
  167. <Icon name="ri-subtract-line" theme={theme && (numberHighlight ? false : true)} />
  168. </span>
  169. </button>
  170. {#if async && asyncLoading}
  171. <div class="px-2 h-9 {roundObj[radius] || 'rounded'} flex flex-col items-center justify-center">
  172. <Loading width="6" height="6" {...loading} />
  173. </div>
  174. {:else}
  175. <div
  176. class="px-4 h-9 {roundObj[radius] || 'rounded'} flex flex-col items-center {injClassNum} justify-center{numberHighlight
  177. ? theme
  178. ? ' bg-primary/5 text-primary dark:bg-dark/10 dark:text-dark'
  179. : ' bg-white dark:bg-black'
  180. : ''}"
  181. >
  182. {value.toFixed(decimal)}
  183. </div>
  184. {/if}
  185. <button
  186. on:click={increaseFn}
  187. class="{vertical ? 'w-full' : 'w-9'} h-9 {roundObj[radius] ||
  188. 'rounded'} flex flex-col items-center {injClassBtn} justify-center{numberHighlight
  189. ? ''
  190. : theme
  191. ? ' bg-primary/5 dark:bg-dark/10'
  192. : ' bg-white dark:bg-black'}"
  193. aria-label="increase"
  194. disabled={async || value === max}
  195. >
  196. <span class={async || value === max ? 'opacity-20' : ''}>
  197. <Icon name="ri-add-line" theme={theme && (numberHighlight ? false : true)} />
  198. </span>
  199. </button>
  200. </div>