@dillingerstaffing/strand-vue 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/dist/components/Alert/Alert.vue.d.ts +31 -0
  2. package/dist/components/Alert/Alert.vue.d.ts.map +1 -0
  3. package/dist/components/Alert/index.d.ts +3 -0
  4. package/dist/components/Alert/index.d.ts.map +1 -0
  5. package/dist/components/Avatar/Avatar.vue.d.ts +20 -0
  6. package/dist/components/Avatar/Avatar.vue.d.ts.map +1 -0
  7. package/dist/components/Avatar/index.d.ts +2 -0
  8. package/dist/components/Avatar/index.d.ts.map +1 -0
  9. package/dist/components/Badge/Badge.vue.d.ts +35 -0
  10. package/dist/components/Badge/Badge.vue.d.ts.map +1 -0
  11. package/dist/components/Badge/index.d.ts +2 -0
  12. package/dist/components/Badge/index.d.ts.map +1 -0
  13. package/dist/components/Breadcrumb/Breadcrumb.vue.d.ts +15 -0
  14. package/dist/components/Breadcrumb/Breadcrumb.vue.d.ts.map +1 -0
  15. package/dist/components/Breadcrumb/index.d.ts +3 -0
  16. package/dist/components/Breadcrumb/index.d.ts.map +1 -0
  17. package/dist/components/Button/Button.vue.d.ts +46 -0
  18. package/dist/components/Button/Button.vue.d.ts.map +1 -0
  19. package/dist/components/Button/index.d.ts +3 -0
  20. package/dist/components/Button/index.d.ts.map +1 -0
  21. package/dist/components/Card/Card.vue.d.ts +30 -0
  22. package/dist/components/Card/Card.vue.d.ts.map +1 -0
  23. package/dist/components/Card/index.d.ts +2 -0
  24. package/dist/components/Card/index.d.ts.map +1 -0
  25. package/dist/components/Checkbox/Checkbox.vue.d.ts +23 -0
  26. package/dist/components/Checkbox/Checkbox.vue.d.ts.map +1 -0
  27. package/dist/components/Checkbox/index.d.ts +3 -0
  28. package/dist/components/Checkbox/index.d.ts.map +1 -0
  29. package/dist/components/Container/Container.vue.d.ts +27 -0
  30. package/dist/components/Container/Container.vue.d.ts.map +1 -0
  31. package/dist/components/Container/index.d.ts +2 -0
  32. package/dist/components/Container/index.d.ts.map +1 -0
  33. package/dist/components/DataReadout/DataReadout.vue.d.ts +15 -0
  34. package/dist/components/DataReadout/DataReadout.vue.d.ts.map +1 -0
  35. package/dist/components/DataReadout/index.d.ts +2 -0
  36. package/dist/components/DataReadout/index.d.ts.map +1 -0
  37. package/dist/components/Dialog/Dialog.vue.d.ts +39 -0
  38. package/dist/components/Dialog/Dialog.vue.d.ts.map +1 -0
  39. package/dist/components/Dialog/index.d.ts +3 -0
  40. package/dist/components/Dialog/index.d.ts.map +1 -0
  41. package/dist/components/Divider/Divider.vue.d.ts +14 -0
  42. package/dist/components/Divider/Divider.vue.d.ts.map +1 -0
  43. package/dist/components/Divider/index.d.ts +2 -0
  44. package/dist/components/Divider/index.d.ts.map +1 -0
  45. package/dist/components/FormField/FormField.vue.d.ts +32 -0
  46. package/dist/components/FormField/FormField.vue.d.ts.map +1 -0
  47. package/dist/components/FormField/index.d.ts +3 -0
  48. package/dist/components/FormField/index.d.ts.map +1 -0
  49. package/dist/components/Grid/Grid.vue.d.ts +30 -0
  50. package/dist/components/Grid/Grid.vue.d.ts.map +1 -0
  51. package/dist/components/Grid/index.d.ts +2 -0
  52. package/dist/components/Grid/index.d.ts.map +1 -0
  53. package/dist/components/Input/Input.vue.d.ts +37 -0
  54. package/dist/components/Input/Input.vue.d.ts.map +1 -0
  55. package/dist/components/Input/index.d.ts +3 -0
  56. package/dist/components/Input/index.d.ts.map +1 -0
  57. package/dist/components/Link/Link.vue.d.ts +29 -0
  58. package/dist/components/Link/Link.vue.d.ts.map +1 -0
  59. package/dist/components/Link/index.d.ts +2 -0
  60. package/dist/components/Link/index.d.ts.map +1 -0
  61. package/dist/components/Nav/Nav.vue.d.ts +30 -0
  62. package/dist/components/Nav/Nav.vue.d.ts.map +1 -0
  63. package/dist/components/Nav/index.d.ts +3 -0
  64. package/dist/components/Nav/index.d.ts.map +1 -0
  65. package/dist/components/Progress/Progress.vue.d.ts +17 -0
  66. package/dist/components/Progress/Progress.vue.d.ts.map +1 -0
  67. package/dist/components/Progress/index.d.ts +2 -0
  68. package/dist/components/Progress/index.d.ts.map +1 -0
  69. package/dist/components/Radio/Radio.vue.d.ts +22 -0
  70. package/dist/components/Radio/Radio.vue.d.ts.map +1 -0
  71. package/dist/components/Radio/index.d.ts +3 -0
  72. package/dist/components/Radio/index.d.ts.map +1 -0
  73. package/dist/components/Section/Section.vue.d.ts +30 -0
  74. package/dist/components/Section/Section.vue.d.ts.map +1 -0
  75. package/dist/components/Section/index.d.ts +2 -0
  76. package/dist/components/Section/index.d.ts.map +1 -0
  77. package/dist/components/Select/Select.vue.d.ts +26 -0
  78. package/dist/components/Select/Select.vue.d.ts.map +1 -0
  79. package/dist/components/Select/index.d.ts +3 -0
  80. package/dist/components/Select/index.d.ts.map +1 -0
  81. package/dist/components/Skeleton/Skeleton.vue.d.ts +16 -0
  82. package/dist/components/Skeleton/Skeleton.vue.d.ts.map +1 -0
  83. package/dist/components/Skeleton/index.d.ts +2 -0
  84. package/dist/components/Skeleton/index.d.ts.map +1 -0
  85. package/dist/components/Slider/Slider.vue.d.ts +24 -0
  86. package/dist/components/Slider/Slider.vue.d.ts.map +1 -0
  87. package/dist/components/Slider/index.d.ts +3 -0
  88. package/dist/components/Slider/index.d.ts.map +1 -0
  89. package/dist/components/Spinner/Spinner.vue.d.ts +12 -0
  90. package/dist/components/Spinner/Spinner.vue.d.ts.map +1 -0
  91. package/dist/components/Spinner/index.d.ts +2 -0
  92. package/dist/components/Spinner/index.d.ts.map +1 -0
  93. package/dist/components/Stack/Stack.vue.d.ts +38 -0
  94. package/dist/components/Stack/Stack.vue.d.ts.map +1 -0
  95. package/dist/components/Stack/index.d.ts +2 -0
  96. package/dist/components/Stack/index.d.ts.map +1 -0
  97. package/dist/components/Switch/Switch.vue.d.ts +18 -0
  98. package/dist/components/Switch/Switch.vue.d.ts.map +1 -0
  99. package/dist/components/Switch/index.d.ts +3 -0
  100. package/dist/components/Switch/index.d.ts.map +1 -0
  101. package/dist/components/Table/Table.vue.d.ts +23 -0
  102. package/dist/components/Table/Table.vue.d.ts.map +1 -0
  103. package/dist/components/Table/index.d.ts +3 -0
  104. package/dist/components/Table/index.d.ts.map +1 -0
  105. package/dist/components/Tabs/Tabs.vue.d.ts +34 -0
  106. package/dist/components/Tabs/Tabs.vue.d.ts.map +1 -0
  107. package/dist/components/Tabs/index.d.ts +3 -0
  108. package/dist/components/Tabs/index.d.ts.map +1 -0
  109. package/dist/components/Tag/Tag.vue.d.ts +37 -0
  110. package/dist/components/Tag/Tag.vue.d.ts.map +1 -0
  111. package/dist/components/Tag/index.d.ts +2 -0
  112. package/dist/components/Tag/index.d.ts.map +1 -0
  113. package/dist/components/Textarea/Textarea.vue.d.ts +29 -0
  114. package/dist/components/Textarea/Textarea.vue.d.ts.map +1 -0
  115. package/dist/components/Textarea/index.d.ts +3 -0
  116. package/dist/components/Textarea/index.d.ts.map +1 -0
  117. package/dist/components/Toast/Toast.vue.d.ts +16 -0
  118. package/dist/components/Toast/Toast.vue.d.ts.map +1 -0
  119. package/dist/components/Toast/ToastProvider.vue.d.ts +18 -0
  120. package/dist/components/Toast/ToastProvider.vue.d.ts.map +1 -0
  121. package/dist/components/Toast/index.d.ts +6 -0
  122. package/dist/components/Toast/index.d.ts.map +1 -0
  123. package/dist/components/Toast/useToast.d.ts +13 -0
  124. package/dist/components/Toast/useToast.d.ts.map +1 -0
  125. package/dist/components/Tooltip/Tooltip.vue.d.ts +29 -0
  126. package/dist/components/Tooltip/Tooltip.vue.d.ts.map +1 -0
  127. package/dist/components/Tooltip/index.d.ts +3 -0
  128. package/dist/components/Tooltip/index.d.ts.map +1 -0
  129. package/dist/css/strand-ui.css +2534 -0
  130. package/dist/index.d.ts +35 -0
  131. package/dist/index.d.ts.map +1 -0
  132. package/dist/index.js +1413 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/test-setup.d.ts +1 -0
  135. package/dist/test-setup.d.ts.map +1 -0
  136. package/package.json +51 -0
  137. package/src/components/Alert/Alert.test.ts +100 -0
  138. package/src/components/Alert/Alert.vue +54 -0
  139. package/src/components/Alert/index.ts +2 -0
  140. package/src/components/Avatar/Avatar.test.ts +105 -0
  141. package/src/components/Avatar/Avatar.vue +56 -0
  142. package/src/components/Avatar/index.ts +1 -0
  143. package/src/components/Badge/Badge.test.ts +114 -0
  144. package/src/components/Badge/Badge.vue +66 -0
  145. package/src/components/Badge/index.ts +1 -0
  146. package/src/components/Breadcrumb/Breadcrumb.test.ts +119 -0
  147. package/src/components/Breadcrumb/Breadcrumb.vue +58 -0
  148. package/src/components/Breadcrumb/index.ts +2 -0
  149. package/src/components/Button/Button.test.ts +148 -0
  150. package/src/components/Button/Button.vue +75 -0
  151. package/src/components/Button/index.ts +2 -0
  152. package/src/components/Card/Card.test.ts +93 -0
  153. package/src/components/Card/Card.vue +36 -0
  154. package/src/components/Card/index.ts +1 -0
  155. package/src/components/Checkbox/Checkbox.test.ts +118 -0
  156. package/src/components/Checkbox/Checkbox.vue +117 -0
  157. package/src/components/Checkbox/index.ts +2 -0
  158. package/src/components/Container/Container.test.ts +70 -0
  159. package/src/components/Container/Container.vue +32 -0
  160. package/src/components/Container/index.ts +1 -0
  161. package/src/components/DataReadout/DataReadout.test.ts +99 -0
  162. package/src/components/DataReadout/DataReadout.vue +36 -0
  163. package/src/components/DataReadout/index.ts +1 -0
  164. package/src/components/Dialog/Dialog.test.ts +224 -0
  165. package/src/components/Dialog/Dialog.vue +146 -0
  166. package/src/components/Dialog/index.ts +2 -0
  167. package/src/components/Divider/Divider.test.ts +95 -0
  168. package/src/components/Divider/Divider.vue +63 -0
  169. package/src/components/Divider/index.ts +1 -0
  170. package/src/components/FormField/FormField.test.ts +98 -0
  171. package/src/components/FormField/FormField.vue +59 -0
  172. package/src/components/FormField/index.ts +2 -0
  173. package/src/components/Grid/Grid.test.ts +73 -0
  174. package/src/components/Grid/Grid.vue +34 -0
  175. package/src/components/Grid/index.ts +1 -0
  176. package/src/components/Input/Input.test.ts +102 -0
  177. package/src/components/Input/Input.vue +63 -0
  178. package/src/components/Input/index.ts +2 -0
  179. package/src/components/Link/Link.test.ts +92 -0
  180. package/src/components/Link/Link.vue +35 -0
  181. package/src/components/Link/index.ts +1 -0
  182. package/src/components/Nav/Nav.test.ts +171 -0
  183. package/src/components/Nav/Nav.vue +81 -0
  184. package/src/components/Nav/index.ts +2 -0
  185. package/src/components/Progress/Progress.test.ts +103 -0
  186. package/src/components/Progress/Progress.vue +96 -0
  187. package/src/components/Progress/index.ts +1 -0
  188. package/src/components/Radio/Radio.test.ts +92 -0
  189. package/src/components/Radio/Radio.vue +60 -0
  190. package/src/components/Radio/index.ts +2 -0
  191. package/src/components/Section/Section.test.ts +77 -0
  192. package/src/components/Section/Section.vue +36 -0
  193. package/src/components/Section/index.ts +1 -0
  194. package/src/components/Select/Select.test.ts +102 -0
  195. package/src/components/Select/Select.vue +70 -0
  196. package/src/components/Select/index.ts +2 -0
  197. package/src/components/Skeleton/Skeleton.test.ts +77 -0
  198. package/src/components/Skeleton/Skeleton.vue +48 -0
  199. package/src/components/Skeleton/index.ts +1 -0
  200. package/src/components/Slider/Slider.test.ts +73 -0
  201. package/src/components/Slider/Slider.vue +60 -0
  202. package/src/components/Slider/index.ts +2 -0
  203. package/src/components/Spinner/Spinner.test.ts +66 -0
  204. package/src/components/Spinner/Spinner.vue +33 -0
  205. package/src/components/Spinner/index.ts +1 -0
  206. package/src/components/Stack/Stack.test.ts +140 -0
  207. package/src/components/Stack/Stack.vue +50 -0
  208. package/src/components/Stack/index.ts +1 -0
  209. package/src/components/Switch/Switch.test.ts +116 -0
  210. package/src/components/Switch/Switch.vue +62 -0
  211. package/src/components/Switch/index.ts +2 -0
  212. package/src/components/Table/Table.test.ts +152 -0
  213. package/src/components/Table/Table.vue +98 -0
  214. package/src/components/Table/index.ts +2 -0
  215. package/src/components/Tabs/Tabs.test.ts +138 -0
  216. package/src/components/Tabs/Tabs.vue +96 -0
  217. package/src/components/Tabs/index.ts +2 -0
  218. package/src/components/Tag/Tag.test.ts +128 -0
  219. package/src/components/Tag/Tag.vue +65 -0
  220. package/src/components/Tag/index.ts +1 -0
  221. package/src/components/Textarea/Textarea.test.ts +107 -0
  222. package/src/components/Textarea/Textarea.vue +90 -0
  223. package/src/components/Textarea/index.ts +2 -0
  224. package/src/components/Toast/Toast.test.ts +204 -0
  225. package/src/components/Toast/Toast.vue +48 -0
  226. package/src/components/Toast/ToastProvider.vue +80 -0
  227. package/src/components/Toast/index.ts +5 -0
  228. package/src/components/Toast/useToast.ts +26 -0
  229. package/src/components/Tooltip/Tooltip.test.ts +145 -0
  230. package/src/components/Tooltip/Tooltip.vue +79 -0
  231. package/src/components/Tooltip/index.ts +2 -0
  232. package/src/index.ts +44 -0
  233. package/src/test-setup.ts +7 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/components/Button/Button.vue","../src/components/Input/Input.vue","../src/components/Textarea/Textarea.vue","../src/components/Select/Select.vue","../src/components/Checkbox/Checkbox.vue","../src/components/Radio/Radio.vue","../src/components/Switch/Switch.vue","../src/components/Slider/Slider.vue","../src/components/FormField/FormField.vue","../src/components/Card/Card.vue","../src/components/Badge/Badge.vue","../src/components/Avatar/Avatar.vue","../src/components/Tag/Tag.vue","../src/components/Table/Table.vue","../src/components/DataReadout/DataReadout.vue","../src/components/Stack/Stack.vue","../src/components/Grid/Grid.vue","../src/components/Container/Container.vue","../src/components/Divider/Divider.vue","../src/components/Section/Section.vue","../src/components/Link/Link.vue","../src/components/Tabs/Tabs.vue","../src/components/Breadcrumb/Breadcrumb.vue","../src/components/Nav/Nav.vue","../src/components/Toast/Toast.vue","../src/components/Toast/useToast.ts","../src/components/Toast/ToastProvider.vue","../src/components/Alert/Alert.vue","../src/components/Dialog/Dialog.vue","../src/components/Tooltip/Tooltip.vue","../src/components/Progress/Progress.vue","../src/components/Spinner/Spinner.vue","../src/components/Skeleton/Skeleton.vue"],"sourcesContent":["<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface ButtonProps {\n /** Visual style variant */\n variant?: 'primary' | 'secondary' | 'ghost' | 'danger'\n /** Button size */\n size?: 'sm' | 'md' | 'lg'\n /** Show loading spinner and disable interaction */\n loading?: boolean\n /** Square button for icon-only use */\n iconOnly?: boolean\n /** HTML button type */\n type?: 'button' | 'submit' | 'reset'\n /** Disabled state */\n disabled?: boolean\n /** Stretch to full container width */\n fullWidth?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'primary',\n size: 'md',\n loading: false,\n iconOnly: false,\n type: 'button',\n disabled: false,\n fullWidth: false,\n})\n\nconst emit = defineEmits<{\n (e: 'click', event: MouseEvent): void\n}>()\n\nconst isDisabled = computed(() => props.disabled || props.loading)\n\nconst classes = computed(() =>\n [\n 'strand-btn',\n `strand-btn--${props.variant}`,\n `strand-btn--${props.size}`,\n props.iconOnly && 'strand-btn--icon-only',\n props.fullWidth && 'strand-btn--full-width',\n props.loading && 'strand-btn--loading',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleClick(event: MouseEvent) {\n if (!isDisabled.value) {\n emit('click', event)\n }\n}\n</script>\n\n<template>\n <button\n :type=\"type\"\n :class=\"classes\"\n :disabled=\"isDisabled\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n @click=\"handleClick\"\n >\n <span v-if=\"loading\" class=\"strand-btn__spinner\" aria-hidden=\"true\" />\n <span\n class=\"strand-btn__content\"\n :style=\"loading ? { visibility: 'hidden' } : undefined\"\n >\n <slot />\n </span>\n </button>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nexport interface InputProps {\n /** Input type */\n type?: 'text' | 'email' | 'password' | 'search' | 'number'\n /** Show error styling */\n error?: boolean\n /** Disabled state */\n disabled?: boolean\n /** Current value */\n modelValue?: string\n}\n\nconst props = withDefaults(defineProps<InputProps>(), {\n type: 'text',\n error: false,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst slots = useSlots()\n\nconst wrapperClasses = computed(() =>\n [\n 'strand-input',\n props.error && 'strand-input--error',\n props.disabled && 'strand-input--disabled',\n !!slots.leading && 'strand-input--has-leading',\n !!slots.trailing && 'strand-input--has-trailing',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.value)\n}\n</script>\n\n<template>\n <div :class=\"wrapperClasses\">\n <span v-if=\"$slots.leading\" class=\"strand-input__leading\" aria-hidden=\"true\">\n <slot name=\"leading\" />\n </span>\n <input\n :type=\"type\"\n class=\"strand-input__field\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n :value=\"modelValue\"\n @input=\"handleInput\"\n />\n <span v-if=\"$slots.trailing\" class=\"strand-input__trailing\" aria-hidden=\"true\">\n <slot name=\"trailing\" />\n </span>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref, onMounted, watch } from 'vue'\n\nexport interface TextareaProps {\n /** Auto-resize to fit content */\n autoResize?: boolean\n /** Show character count (requires maxLength) */\n showCount?: boolean\n /** Show error styling */\n error?: boolean\n /** Maximum character count */\n maxLength?: number\n /** Disabled state */\n disabled?: boolean\n /** Controlled value */\n modelValue?: string\n}\n\nconst props = withDefaults(defineProps<TextareaProps>(), {\n autoResize: false,\n showCount: false,\n error: false,\n disabled: false,\n modelValue: '',\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst textareaRef = ref<HTMLTextAreaElement | null>(null)\n\nconst wrapperClasses = computed(() =>\n [\n 'strand-textarea',\n props.error && 'strand-textarea--error',\n props.disabled && 'strand-textarea--disabled',\n props.autoResize && 'strand-textarea--auto-resize',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nconst currentLength = computed(() =>\n typeof props.modelValue === 'string' ? props.modelValue.length : 0,\n)\n\nfunction resize() {\n if (props.autoResize && textareaRef.value) {\n textareaRef.value.style.height = 'auto'\n textareaRef.value.style.height = `${textareaRef.value.scrollHeight}px`\n }\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLTextAreaElement\n emit('update:modelValue', target.value)\n resize()\n}\n\nwatch(() => props.modelValue, () => {\n resize()\n})\n\nonMounted(() => {\n resize()\n})\n</script>\n\n<template>\n <div :class=\"wrapperClasses\">\n <textarea\n ref=\"textareaRef\"\n class=\"strand-textarea__field\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n :maxlength=\"maxLength\"\n :value=\"modelValue\"\n @input=\"handleInput\"\n />\n <span\n v-if=\"showCount && maxLength != null\"\n class=\"strand-textarea__count\"\n aria-live=\"polite\"\n >\n {{ currentLength }}/{{ maxLength }}\n </span>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface SelectOption {\n value: string\n label: string\n}\n\nexport interface SelectProps {\n /** Array of options to display */\n options: SelectOption[]\n /** Disabled state */\n disabled?: boolean\n /** Currently selected value */\n modelValue?: string\n /** Show error styling */\n error?: boolean\n /** Placeholder text shown as first disabled option */\n placeholder?: string\n}\n\nconst props = withDefaults(defineProps<SelectProps>(), {\n disabled: false,\n error: false,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst wrapperClasses = computed(() =>\n [\n 'strand-select',\n props.error && 'strand-select--error',\n props.disabled && 'strand-select--disabled',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n emit('update:modelValue', target.value)\n}\n</script>\n\n<template>\n <div :class=\"wrapperClasses\">\n <select\n class=\"strand-select__field\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error ? 'true' : undefined\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"opt in options\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </option>\n </select>\n <span class=\"strand-select__arrow\" aria-hidden=\"true\" />\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref, watch, onMounted } from 'vue'\n\nexport interface CheckboxProps {\n /** Controlled checked state */\n checked?: boolean\n /** Indeterminate visual state */\n indeterminate?: boolean\n /** Disabled state */\n disabled?: boolean\n /** Label text */\n label?: string\n}\n\nconst props = withDefaults(defineProps<CheckboxProps>(), {\n checked: false,\n indeterminate: false,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n (e: 'change', event: Event): void\n}>()\n\nconst inputRef = ref<HTMLInputElement | null>(null)\n\nonMounted(() => {\n if (inputRef.value) {\n inputRef.value.indeterminate = props.indeterminate\n }\n})\n\nwatch(() => props.indeterminate, (val) => {\n if (inputRef.value) {\n inputRef.value.indeterminate = val\n }\n})\n\nconst classes = computed(() =>\n [\n 'strand-checkbox',\n props.checked && 'strand-checkbox--checked',\n props.indeterminate && 'strand-checkbox--indeterminate',\n props.disabled && 'strand-checkbox--disabled',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nconst ariaChecked = computed(() =>\n props.indeterminate ? 'mixed' : props.checked ? 'true' : 'false',\n)\n\nfunction handleChange(event: Event) {\n if (!props.disabled) {\n emit('change', event)\n }\n}\n\nfunction handleKeyDown(event: KeyboardEvent) {\n if (event.key === ' ' && !props.disabled) {\n event.preventDefault()\n if (inputRef.value) {\n inputRef.value.click()\n }\n }\n}\n</script>\n\n<template>\n <label :class=\"classes\" @keydown=\"handleKeyDown\">\n <input\n ref=\"inputRef\"\n type=\"checkbox\"\n class=\"strand-checkbox__native\"\n :checked=\"checked\"\n :disabled=\"disabled\"\n :aria-checked=\"ariaChecked\"\n role=\"checkbox\"\n @change=\"handleChange\"\n />\n <span class=\"strand-checkbox__control\" aria-hidden=\"true\">\n <svg\n v-if=\"indeterminate\"\n class=\"strand-checkbox__icon\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <line\n x1=\"4\"\n y1=\"8\"\n x2=\"12\"\n y2=\"8\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n />\n </svg>\n <svg\n v-else-if=\"checked\"\n class=\"strand-checkbox__icon\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 8L6.5 11L12.5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n <span v-if=\"label\" class=\"strand-checkbox__label\">{{ label }}</span>\n </label>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface RadioProps {\n /** Controlled checked state */\n checked?: boolean\n /** Disabled state */\n disabled?: boolean\n /** Label text */\n label?: string\n /** Radio group name */\n name?: string\n /** Radio value */\n value?: string\n}\n\nconst props = withDefaults(defineProps<RadioProps>(), {\n checked: false,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n (e: 'change', event: Event): void\n}>()\n\nconst classes = computed(() =>\n [\n 'strand-radio',\n props.checked && 'strand-radio--checked',\n props.disabled && 'strand-radio--disabled',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleChange(event: Event) {\n if (!props.disabled) {\n emit('change', event)\n }\n}\n</script>\n\n<template>\n <label :class=\"classes\">\n <input\n type=\"radio\"\n class=\"strand-radio__native\"\n :checked=\"checked\"\n :disabled=\"disabled\"\n :name=\"name\"\n :value=\"value\"\n @change=\"handleChange\"\n />\n <span class=\"strand-radio__control\" aria-hidden=\"true\">\n <span class=\"strand-radio__dot\" />\n </span>\n <span v-if=\"label\" class=\"strand-radio__label\">{{ label }}</span>\n </label>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface SwitchProps {\n /** Controlled checked state */\n checked?: boolean\n /** Disabled state */\n disabled?: boolean\n /** Inline label text */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SwitchProps>(), {\n checked: false,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n (e: 'change', checked: boolean): void\n}>()\n\nconst classes = computed(() =>\n [\n 'strand-switch',\n props.checked && 'strand-switch--checked',\n props.disabled && 'strand-switch--disabled',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleClick() {\n if (!props.disabled) {\n emit('change', !props.checked)\n }\n}\n\nfunction handleKeyDown(event: KeyboardEvent) {\n if ((event.key === ' ' || event.key === 'Enter') && !props.disabled) {\n event.preventDefault()\n emit('change', !props.checked)\n }\n}\n</script>\n\n<template>\n <label :class=\"classes\">\n <button\n type=\"button\"\n role=\"switch\"\n class=\"strand-switch__track\"\n :aria-checked=\"checked ? 'true' : 'false'\"\n :disabled=\"disabled\"\n @click=\"handleClick\"\n @keydown=\"handleKeyDown\"\n >\n <span class=\"strand-switch__thumb\" aria-hidden=\"true\" />\n </button>\n <span v-if=\"label\" class=\"strand-switch__label\">{{ label }}</span>\n </label>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface SliderProps {\n /** Minimum value */\n min?: number\n /** Maximum value */\n max?: number\n /** Step increment */\n step?: number\n /** Current value */\n modelValue?: number\n /** Disabled state */\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<SliderProps>(), {\n min: 0,\n max: 100,\n step: 1,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: number): void\n}>()\n\nconst wrapperClasses = computed(() =>\n [\n 'strand-slider',\n props.disabled && 'strand-slider--disabled',\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', Number(target.value))\n}\n</script>\n\n<template>\n <div :class=\"wrapperClasses\">\n <input\n type=\"range\"\n class=\"strand-slider__field\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"modelValue\"\n @input=\"handleInput\"\n />\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface FormFieldProps {\n /** Label text */\n label: string\n /** Associates the label with a form control */\n htmlFor: string\n /** Hint text displayed below the input */\n hint?: string\n /** Error text displayed below the input (replaces hint) */\n error?: string\n /** Show required indicator */\n required?: boolean\n}\n\nconst props = withDefaults(defineProps<FormFieldProps>(), {\n required: false,\n})\n\nconst classes = computed(() =>\n [\n 'strand-form-field',\n props.error && 'strand-form-field--error',\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <div :class=\"classes\">\n <label class=\"strand-form-field__label\" :for=\"htmlFor\">\n {{ label }}\n <span v-if=\"required\" class=\"strand-form-field__required\" aria-hidden=\"true\">\n *\n </span>\n </label>\n <div class=\"strand-form-field__control\">\n <slot />\n </div>\n <p\n v-if=\"error\"\n class=\"strand-form-field__error\"\n :id=\"`${htmlFor}-error`\"\n role=\"alert\"\n >\n {{ error }}\n </p>\n <p\n v-else-if=\"hint\"\n class=\"strand-form-field__hint\"\n :id=\"`${htmlFor}-hint`\"\n >\n {{ hint }}\n </p>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Visual style variant */\n variant?: 'elevated' | 'outlined' | 'interactive'\n /** Inner padding */\n padding?: 'none' | 'sm' | 'md' | 'lg'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'elevated',\n padding: 'md',\n className: '',\n})\n\nconst classes = computed(() =>\n [\n 'strand-card',\n `strand-card--${props.variant}`,\n `strand-card--pad-${props.padding}`,\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <div :class=\"classes\" v-bind=\"$attrs\">\n <slot />\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\ninterface Props {\n /** Badge display mode */\n variant?: 'dot' | 'count'\n /** Color status */\n status?: 'default' | 'teal' | 'blue' | 'amber' | 'red'\n /** Number to display (count variant only) */\n count?: number\n /** Maximum count before showing \"N+\" */\n maxCount?: number\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'count',\n status: 'default',\n maxCount: 99,\n className: '',\n})\n\nconst slots = useSlots()\n\nconst hasChildren = computed(() => !!slots.default)\n\nconst displayValue = computed(() => {\n if (props.variant === 'count') {\n return props.count != null && props.count > props.maxCount\n ? `${props.maxCount}+`\n : props.count\n }\n return null\n})\n\nconst ariaLabel = computed(() => {\n if (props.variant === 'dot') return 'Status indicator'\n if (props.count != null) return `${props.count} notifications`\n return undefined\n})\n\nconst badgeClasses = computed(() =>\n [\n 'strand-badge__indicator',\n `strand-badge--${props.variant}`,\n `strand-badge--${props.status}`,\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nconst wrapperClasses = computed(() =>\n hasChildren.value\n ? ['strand-badge', props.className].filter(Boolean).join(' ')\n : ['strand-badge', 'strand-badge--inline', props.className].filter(Boolean).join(' '),\n)\n</script>\n\n<template>\n <span :class=\"wrapperClasses\" v-bind=\"$attrs\">\n <slot />\n <span :class=\"badgeClasses\" :aria-label=\"ariaLabel\" role=\"status\">{{ displayValue }}</span>\n </span>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\ninterface Props {\n /** Image URL */\n src?: string\n /** Alt text for image */\n alt?: string\n /** Fallback initials (1-2 characters) */\n initials?: string\n /** Avatar size */\n size?: 'sm' | 'md' | 'lg' | 'xl'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n alt: '',\n initials: '',\n size: 'md',\n className: '',\n})\n\nconst imgError = ref(false)\n\nconst handleError = () => {\n imgError.value = true\n}\n\nconst showImage = computed(() => props.src && !imgError.value)\nconst displayInitials = computed(() => props.initials.slice(0, 2).toUpperCase())\n\nconst classes = computed(() =>\n [\n 'strand-avatar',\n `strand-avatar--${props.size}`,\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <div :class=\"classes\" role=\"img\" :aria-label=\"alt || displayInitials\" v-bind=\"$attrs\">\n <img\n v-if=\"showImage\"\n class=\"strand-avatar__img\"\n :src=\"src\"\n :alt=\"alt\"\n @error=\"handleError\"\n />\n <span v-else class=\"strand-avatar__initials\" aria-hidden=\"true\">{{ displayInitials }}</span>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Visual style variant */\n variant?: 'solid' | 'outlined'\n /** Color status */\n status?: 'default' | 'teal' | 'blue' | 'amber' | 'red'\n /** Show remove button */\n removable?: boolean\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'solid',\n status: 'default',\n removable: false,\n className: '',\n})\n\nconst emit = defineEmits<{\n remove: []\n}>()\n\nconst classes = computed(() =>\n [\n 'strand-tag',\n `strand-tag--${props.variant}`,\n `strand-tag--${props.status}`,\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <span :class=\"classes\" v-bind=\"$attrs\">\n <span class=\"strand-tag__text\"><slot /></span>\n <button\n v-if=\"removable\"\n type=\"button\"\n class=\"strand-tag__remove\"\n aria-label=\"Remove\"\n @click=\"emit('remove')\"\n >\n <svg\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 12 12\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3 3l6 6M9 3l-6 6\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </span>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nexport interface TableColumn {\n /** Unique key matching the data field */\n key: string\n /** Display header text */\n header: string\n /** Whether the column is sortable */\n sortable?: boolean\n /** Optional fixed width */\n width?: string\n}\n\nexport interface TableProps {\n /** Column definitions */\n columns: TableColumn[]\n /** Row data */\n data: Array<Record<string, unknown>>\n}\n\nconst props = defineProps<TableProps>()\n\nconst emit = defineEmits<{\n (e: 'sort', key: string, direction: 'asc' | 'desc'): void\n}>()\n\nconst sortKey = ref<string | null>(null)\nconst sortDirection = ref<'asc' | 'desc'>('asc')\n\nconst wrapperClasses = computed(() =>\n ['strand-table-wrapper'].filter(Boolean).join(' '),\n)\n\nfunction handleSort(key: string) {\n const nextDirection =\n sortKey.value === key && sortDirection.value === 'asc' ? 'desc' : 'asc'\n sortKey.value = key\n sortDirection.value = nextDirection\n emit('sort', key, nextDirection)\n}\n\nfunction sortIndicator(key: string): string {\n if (sortKey.value === key) {\n return sortDirection.value === 'asc' ? '\\u2191' : '\\u2193'\n }\n return '\\u2195'\n}\n</script>\n\n<template>\n <div :class=\"wrapperClasses\">\n <table class=\"strand-table\">\n <thead class=\"strand-table__head\">\n <tr>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n class=\"strand-table__th\"\n :style=\"col.width ? { width: col.width } : undefined\"\n >\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n class=\"strand-table__sort-btn\"\n :aria-label=\"`Sort by ${col.header}`\"\n @click=\"handleSort(col.key)\"\n >\n {{ col.header }}\n <span class=\"strand-table__sort-indicator\" aria-hidden=\"true\">\n {{ sortIndicator(col.key) }}\n </span>\n </button>\n <template v-else>\n {{ col.header }}\n </template>\n </th>\n </tr>\n </thead>\n <tbody class=\"strand-table__body\">\n <tr\n v-for=\"(row, rowIndex) in data\"\n :key=\"rowIndex\"\n class=\"strand-table__row\"\n >\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n class=\"strand-table__td\"\n >\n {{ row[col.key] }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Overline label text */\n label: string\n /** The large displayed value */\n value: string | number\n /** Size variant: sm (compact), md (default), lg (hero) */\n size?: 'sm' | 'md' | 'lg'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n className: '',\n})\n\nconst classes = computed(() =>\n [\n 'strand-data-readout',\n props.size && props.size !== 'md' ? `strand-data-readout--${props.size}` : '',\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <div :class=\"classes\" v-bind=\"$attrs\">\n <span class=\"strand-data-readout__label\">{{ label }}</span>\n <span class=\"strand-data-readout__value\">{{ value }}</span>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Flex direction */\n direction?: 'vertical' | 'horizontal'\n /** Gap between items, maps to --strand-space-{n} */\n gap?: number\n /** Cross-axis alignment */\n align?: 'start' | 'center' | 'end' | 'stretch'\n /** Main-axis alignment */\n justify?: 'start' | 'center' | 'end' | 'between' | 'around'\n /** Enable flex-wrap */\n wrap?: boolean\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n direction: 'vertical',\n gap: 4,\n align: 'stretch',\n wrap: false,\n className: '',\n})\n\nconst classes = computed(() =>\n [\n 'strand-stack',\n `strand-stack--${props.direction}`,\n props.align !== 'stretch' && `strand-stack--align-${props.align}`,\n props.justify && `strand-stack--justify-${props.justify}`,\n props.wrap && 'strand-stack--wrap',\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nconst inlineStyle = computed(() => ({\n gap: `var(--strand-space-${props.gap})`,\n}))\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"inlineStyle\" v-bind=\"$attrs\">\n <slot />\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Number of equal-width columns */\n columns?: number\n /** Gap between items, maps to --strand-space-{n} */\n gap?: number\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n columns: 1,\n gap: 4,\n className: '',\n})\n\nconst classes = computed(() =>\n ['strand-grid', props.className].filter(Boolean).join(' '),\n)\n\nconst inlineStyle = computed(() => ({\n gridTemplateColumns: `repeat(${props.columns}, 1fr)`,\n gap: `var(--strand-space-${props.gap})`,\n}))\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"inlineStyle\" v-bind=\"$attrs\">\n <slot />\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Max-width constraint */\n size?: 'narrow' | 'default' | 'wide' | 'full'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'default',\n className: '',\n})\n\nconst classes = computed(() =>\n [\n 'strand-container',\n `strand-container--${props.size}`,\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <div :class=\"classes\" v-bind=\"$attrs\">\n <slot />\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Separator direction */\n direction?: 'horizontal' | 'vertical'\n /** Optional label text displayed in the middle of the line */\n label?: string\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n direction: 'horizontal',\n className: '',\n})\n\nconst isVertical = computed(() => props.direction === 'vertical')\nconst isLabeled = computed(() => !isVertical.value && !!props.label)\nconst isPlainHorizontal = computed(() => !isVertical.value && !props.label)\n\nconst classes = computed(() => {\n if (isVertical.value) {\n return ['strand-divider', 'strand-divider--vertical', props.className]\n .filter(Boolean)\n .join(' ')\n }\n if (isLabeled.value) {\n return ['strand-divider', 'strand-divider--horizontal', 'strand-divider--labeled', props.className]\n .filter(Boolean)\n .join(' ')\n }\n return ['strand-divider', 'strand-divider--horizontal', props.className]\n .filter(Boolean)\n .join(' ')\n})\n</script>\n\n<template>\n <div\n v-if=\"isVertical\"\n :class=\"classes\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n <div\n v-else-if=\"isLabeled\"\n :class=\"classes\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n >\n <span class=\"strand-divider__line\" />\n <span class=\"strand-divider__label\">{{ label }}</span>\n <span class=\"strand-divider__line\" />\n </div>\n <hr\n v-else\n :class=\"classes\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n />\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Padding variant */\n variant?: 'standard' | 'hero'\n /** Surface background */\n background?: 'primary' | 'elevated' | 'recessed'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'standard',\n background: 'primary',\n className: '',\n})\n\nconst classes = computed(() =>\n [\n 'strand-section',\n `strand-section--${props.variant}`,\n `strand-section--bg-${props.background}`,\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <section :class=\"classes\" v-bind=\"$attrs\">\n <slot />\n </section>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** URL destination */\n href: string\n /** Opens in new tab with rel=\"noopener noreferrer\" */\n external?: boolean\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n external: false,\n className: '',\n})\n\nconst classes = computed(() =>\n ['strand-link', props.className].filter(Boolean).join(' '),\n)\n</script>\n\n<template>\n <a\n :href=\"href\"\n :class=\"classes\"\n v-bind=\"{\n ...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {}),\n ...$attrs,\n }\"\n >\n <slot />\n </a>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nexport interface TabItem {\n id: string\n label: string\n}\n\nexport interface TabsProps {\n /** Tab definitions (id + label only; content is provided via slots) */\n tabs: TabItem[]\n /** Currently active tab id (controlled) */\n activeTab: string\n}\n\nconst props = defineProps<TabsProps>()\n\nconst emit = defineEmits<{\n (e: 'change', id: string): void\n}>()\n\nconst tablistRef = ref<HTMLDivElement | null>(null)\n\nconst classes = computed(() => ['strand-tabs'].filter(Boolean).join(' '))\n\nfunction focusAndSelect(index: number) {\n const tab = props.tabs[index]\n if (tab) {\n emit('change', tab.id)\n const buttons = tablistRef.value?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n buttons?.[index]?.focus()\n }\n}\n\nfunction handleKeyDown(event: KeyboardEvent) {\n const currentIndex = props.tabs.findIndex((t) => t.id === props.activeTab)\n let nextIndex: number | null = null\n\n switch (event.key) {\n case 'ArrowRight':\n nextIndex = (currentIndex + 1) % props.tabs.length\n break\n case 'ArrowLeft':\n nextIndex = (currentIndex - 1 + props.tabs.length) % props.tabs.length\n break\n case 'Home':\n nextIndex = 0\n break\n case 'End':\n nextIndex = props.tabs.length - 1\n break\n default:\n return\n }\n\n event.preventDefault()\n focusAndSelect(nextIndex)\n}\n</script>\n\n<template>\n <div :class=\"classes\">\n <div ref=\"tablistRef\" role=\"tablist\" @keydown=\"handleKeyDown\">\n <button\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n :id=\"`tab-${tab.id}`\"\n role=\"tab\"\n type=\"button\"\n :class=\"[\n 'strand-tabs__tab',\n tab.id === activeTab && 'strand-tabs__tab--active',\n ].filter(Boolean).join(' ')\"\n :aria-selected=\"tab.id === activeTab ? 'true' : 'false'\"\n :aria-controls=\"`panel-${tab.id}`\"\n :tabindex=\"tab.id === activeTab ? 0 : -1\"\n @click=\"emit('change', tab.id)\"\n >\n {{ tab.label }}\n </button>\n </div>\n\n <div\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n :id=\"`panel-${tab.id}`\"\n role=\"tabpanel\"\n :aria-labelledby=\"`tab-${tab.id}`\"\n :hidden=\"tab.id !== activeTab || undefined\"\n :tabindex=\"0\"\n >\n <slot :name=\"`panel-${tab.id}`\" />\n </div>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface BreadcrumbItem {\n label: string\n href?: string\n}\n\nexport interface BreadcrumbProps {\n /** Breadcrumb path items; last item is treated as current page */\n items: BreadcrumbItem[]\n /** Separator character between items */\n separator?: string\n}\n\nconst props = withDefaults(defineProps<BreadcrumbProps>(), {\n separator: '/',\n})\n\nconst classes = computed(() =>\n ['strand-breadcrumb'].filter(Boolean).join(' '),\n)\n</script>\n\n<template>\n <nav aria-label=\"Breadcrumb\" :class=\"classes\">\n <ol class=\"strand-breadcrumb__list\">\n <li\n v-for=\"(item, index) in items\"\n :key=\"`${item.label}-${index}`\"\n class=\"strand-breadcrumb__item\"\n >\n <span\n v-if=\"index > 0\"\n class=\"strand-breadcrumb__separator\"\n aria-hidden=\"true\"\n >\n {{ separator }}\n </span>\n <span\n v-if=\"index === items.length - 1\"\n class=\"strand-breadcrumb__current\"\n aria-current=\"page\"\n >\n {{ item.label }}\n </span>\n <a\n v-else\n :href=\"item.href\"\n class=\"strand-breadcrumb__link\"\n >\n {{ item.label }}\n </a>\n </li>\n </ol>\n </nav>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nexport interface NavItem {\n label: string\n href: string\n active?: boolean\n}\n\nexport interface NavProps {\n /** Navigation items */\n items?: NavItem[]\n}\n\nconst props = withDefaults(defineProps<NavProps>(), {\n items: () => [],\n})\n\nconst menuOpen = ref(false)\n\nfunction toggleMenu() {\n menuOpen.value = !menuOpen.value\n}\n\nconst classes = computed(() => ['strand-nav'].filter(Boolean).join(' '))\n</script>\n\n<template>\n <nav :class=\"classes\" aria-label=\"Main navigation\">\n <div class=\"strand-nav__inner\">\n <div v-if=\"$slots.logo\" class=\"strand-nav__logo\">\n <slot name=\"logo\" />\n </div>\n\n <div class=\"strand-nav__items\">\n <a\n v-for=\"item in items\"\n :key=\"item.href\"\n :href=\"item.href\"\n :class=\"[\n 'strand-nav__link',\n item.active && 'strand-nav__link--active',\n ].filter(Boolean).join(' ')\"\n :aria-current=\"item.active ? 'page' : undefined\"\n >\n {{ item.label }}\n </a>\n </div>\n\n <div v-if=\"$slots.actions\" class=\"strand-nav__actions\">\n <slot name=\"actions\" />\n </div>\n\n <button\n type=\"button\"\n class=\"strand-nav__hamburger\"\n :aria-expanded=\"menuOpen ? 'true' : 'false'\"\n :aria-label=\"menuOpen ? 'Close menu' : 'Menu'\"\n @click=\"toggleMenu\"\n >\n <span class=\"strand-nav__hamburger-icon\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div v-if=\"menuOpen\" class=\"strand-nav__mobile-menu\">\n <a\n v-for=\"item in items\"\n :key=\"item.href\"\n :href=\"item.href\"\n :class=\"[\n 'strand-nav__mobile-link',\n item.active && 'strand-nav__mobile-link--active',\n ].filter(Boolean).join(' ')\"\n :aria-current=\"item.active ? 'page' : undefined\"\n >\n {{ item.label }}\n </a>\n </div>\n </nav>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { ToastStatus } from './useToast'\n\nexport interface ToastProps {\n /** Visual status */\n status?: ToastStatus\n /** Toast message text */\n message: string\n}\n\nconst props = withDefaults(defineProps<ToastProps>(), {\n status: 'info',\n})\n\nconst emit = defineEmits<{\n (e: 'dismiss'): void\n}>()\n\nconst isUrgent = computed(\n () => props.status === 'error' || props.status === 'warning',\n)\n\nconst classes = computed(() =>\n ['strand-toast', `strand-toast--${props.status}`]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <div\n :class=\"classes\"\n role=\"status\"\n :aria-live=\"isUrgent ? 'assertive' : 'polite'\"\n >\n <span class=\"strand-toast__message\">{{ message }}</span>\n <button\n type=\"button\"\n class=\"strand-toast__dismiss\"\n aria-label=\"Dismiss\"\n @click=\"emit('dismiss')\"\n >\n &#215;\n </button>\n </div>\n</template>\n","/*! Strand Vue | MIT License | dillingerstaffing.com */\n\nimport { inject } from 'vue'\nimport type { InjectionKey } from 'vue'\n\nexport type ToastStatus = 'info' | 'success' | 'warning' | 'error'\n\nexport interface ToastOptions {\n message: string\n status?: ToastStatus\n duration?: number\n}\n\nexport interface ToastContextValue {\n toast: (options: ToastOptions) => void\n}\n\nexport const ToastKey: InjectionKey<ToastContextValue> = Symbol('StrandToast')\n\nexport function useToast(): ToastContextValue {\n const ctx = inject(ToastKey)\n if (!ctx) {\n throw new Error('useToast must be used within a ToastProvider')\n }\n return ctx\n}\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { ref, provide, onUnmounted } from 'vue'\nimport { ToastKey } from './useToast'\nimport type { ToastOptions, ToastStatus } from './useToast'\n\ninterface ToastEntry {\n id: number\n message: string\n status: ToastStatus\n duration: number\n}\n\nlet toastIdCounter = 0\n\nconst toasts = ref<ToastEntry[]>([])\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n\nfunction removeToast(id: number) {\n const timer = timers.get(id)\n if (timer !== undefined) {\n clearTimeout(timer)\n timers.delete(id)\n }\n toasts.value = toasts.value.filter((t) => t.id !== id)\n}\n\nfunction addToast(options: ToastOptions) {\n const entry: ToastEntry = {\n id: ++toastIdCounter,\n message: options.message,\n status: options.status ?? 'info',\n duration: options.duration ?? 5000,\n }\n toasts.value = [...toasts.value, entry]\n\n if (entry.duration > 0) {\n const timer = setTimeout(() => {\n removeToast(entry.id)\n }, entry.duration)\n timers.set(entry.id, timer)\n }\n}\n\nprovide(ToastKey, { toast: addToast })\n\nonUnmounted(() => {\n for (const timer of timers.values()) {\n clearTimeout(timer)\n }\n timers.clear()\n})\n\nfunction isUrgent(status: ToastStatus): boolean {\n return status === 'error' || status === 'warning'\n}\n</script>\n\n<template>\n <slot />\n <div v-if=\"toasts.length > 0\" class=\"strand-toast__container\">\n <div\n v-for=\"entry in toasts\"\n :key=\"entry.id\"\n :class=\"['strand-toast', `strand-toast--${entry.status}`].join(' ')\"\n role=\"status\"\n :aria-live=\"isUrgent(entry.status) ? 'assertive' : 'polite'\"\n >\n <span class=\"strand-toast__message\">{{ entry.message }}</span>\n <button\n type=\"button\"\n class=\"strand-toast__dismiss\"\n aria-label=\"Dismiss\"\n @click=\"removeToast(entry.id)\"\n >\n &#215;\n </button>\n </div>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface AlertProps {\n /** Visual status of the alert */\n status?: 'info' | 'success' | 'warning' | 'error'\n /** Show dismiss button */\n dismissible?: boolean\n}\n\nconst props = withDefaults(defineProps<AlertProps>(), {\n status: 'info',\n dismissible: false,\n})\n\nconst emit = defineEmits<{\n (e: 'dismiss'): void\n}>()\n\nconst role = computed(() =>\n props.status === 'error' || props.status === 'warning' ? 'alert' : 'status',\n)\n\nconst classes = computed(() =>\n [\n 'strand-alert',\n `strand-alert--${props.status}`,\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nfunction handleDismiss() {\n emit('dismiss')\n}\n</script>\n\n<template>\n <div :class=\"classes\" :role=\"role\">\n <div class=\"strand-alert__content\">\n <slot />\n </div>\n <button\n v-if=\"dismissible\"\n type=\"button\"\n class=\"strand-alert__dismiss\"\n aria-label=\"Dismiss\"\n @click=\"handleDismiss\"\n >\n &#215;\n </button>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref, watch, onUnmounted, nextTick } from 'vue'\n\nexport interface DialogProps {\n /** Whether the dialog is open */\n open: boolean\n /** Optional title rendered in the dialog header */\n title?: string\n /** Close when clicking the backdrop */\n closeOnOutsideClick?: boolean\n /** Close when pressing Escape */\n closeOnEscape?: boolean\n}\n\nconst props = withDefaults(defineProps<DialogProps>(), {\n closeOnOutsideClick: true,\n closeOnEscape: true,\n})\n\nconst emit = defineEmits<{\n (e: 'close'): void\n}>()\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], button:not(:disabled), textarea:not(:disabled), input:not(:disabled), select:not(:disabled), [tabindex]:not([tabindex=\"-1\"])'\n\nlet dialogIdCounter = 0\nconst titleId = `strand-dialog-title-${++dialogIdCounter}`\n\nconst panelRef = ref<HTMLDivElement | null>(null)\nlet previousFocus: Element | null = null\nlet originalOverflow = ''\n\nconst panelClasses = computed(() =>\n ['strand-dialog__panel'].filter(Boolean).join(' '),\n)\n\nfunction handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape' && props.closeOnEscape) {\n event.stopPropagation()\n emit('close')\n return\n }\n\n if (event.key === 'Tab') {\n const panel = panelRef.value\n if (!panel) return\n\n const focusable = Array.from(\n panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n )\n if (focusable.length === 0) return\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (event.shiftKey) {\n if (document.activeElement === first) {\n event.preventDefault()\n last.focus()\n }\n } else {\n if (document.activeElement === last) {\n event.preventDefault()\n first.focus()\n }\n }\n }\n}\n\nfunction handleBackdropClick(event: MouseEvent) {\n if (props.closeOnOutsideClick && event.target === event.currentTarget) {\n emit('close')\n }\n}\n\nwatch(\n () => props.open,\n async (isOpen) => {\n if (isOpen) {\n previousFocus = document.activeElement\n originalOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n\n await nextTick()\n const panel = panelRef.value\n if (panel) {\n const focusable = panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)\n if (focusable.length > 0) {\n focusable[0].focus()\n } else {\n panel.focus()\n }\n }\n } else {\n document.body.style.overflow = originalOverflow\n if (previousFocus && previousFocus instanceof HTMLElement) {\n previousFocus.focus()\n }\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (props.open) {\n document.body.style.overflow = originalOverflow\n }\n})\n</script>\n\n<template>\n <div\n v-if=\"open\"\n class=\"strand-dialog__backdrop\"\n @click=\"handleBackdropClick\"\n @keydown=\"handleKeyDown\"\n >\n <div\n ref=\"panelRef\"\n :class=\"panelClasses\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-labelledby=\"title ? titleId : undefined\"\n :tabindex=\"-1\"\n >\n <div v-if=\"title\" class=\"strand-dialog__header\">\n <h2 :id=\"titleId\" class=\"strand-dialog__title\">\n {{ title }}\n </h2>\n </div>\n <button\n type=\"button\"\n class=\"strand-dialog__close\"\n aria-label=\"Close\"\n @click=\"emit('close')\"\n >\n &#215;\n </button>\n <div class=\"strand-dialog__body\">\n <slot />\n </div>\n </div>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed, ref, onUnmounted } from 'vue'\n\nexport interface TooltipProps {\n /** Tooltip text */\n content: string\n /** Position relative to trigger */\n position?: 'top' | 'right' | 'bottom' | 'left'\n /** Delay in ms before showing */\n delay?: number\n}\n\nconst props = withDefaults(defineProps<TooltipProps>(), {\n position: 'top',\n delay: 200,\n})\n\nlet tooltipIdCounter = 0\nconst tooltipId = `strand-tooltip-${++tooltipIdCounter}`\n\nconst visible = ref(false)\nlet timer: ReturnType<typeof setTimeout> | null = null\n\nfunction show() {\n timer = setTimeout(() => {\n visible.value = true\n }, props.delay)\n}\n\nfunction hide() {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n visible.value = false\n}\n\nonUnmounted(() => {\n if (timer !== null) {\n clearTimeout(timer)\n }\n})\n\nconst wrapperClasses = computed(() =>\n ['strand-tooltip__wrapper'].filter(Boolean).join(' '),\n)\n\nconst tooltipClasses = computed(() =>\n [\n 'strand-tooltip',\n `strand-tooltip--${props.position}`,\n visible.value && 'strand-tooltip--visible',\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <span\n :class=\"wrapperClasses\"\n :aria-describedby=\"tooltipId\"\n @mouseenter=\"show\"\n @mouseleave=\"hide\"\n @focus=\"show\"\n @blur=\"hide\"\n >\n <slot />\n <span\n :id=\"tooltipId\"\n :class=\"tooltipClasses\"\n role=\"tooltip\"\n :aria-hidden=\"!visible\"\n >\n {{ content }}\n </span>\n </span>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Visual variant */\n variant?: 'bar' | 'ring'\n /** Completion percentage (0-100). Omit for indeterminate. */\n value?: number\n /** Size of the progress indicator */\n size?: 'sm' | 'md' | 'lg'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'bar',\n size: 'md',\n className: '',\n})\n\nconst RING_SIZES: Record<string, number> = { sm: 24, md: 40, lg: 56 }\nconst RING_STROKE = 3\n\nconst isDeterminate = computed(() => props.value != null)\n\nconst classes = computed(() =>\n [\n 'strand-progress',\n `strand-progress--${props.variant}`,\n `strand-progress--${props.size}`,\n !isDeterminate.value && 'strand-progress--indeterminate',\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nconst dim = computed(() => RING_SIZES[props.size] ?? RING_SIZES.md)\nconst radius = computed(() => (dim.value - RING_STROKE) / 2)\nconst circumference = computed(() => 2 * Math.PI * radius.value)\nconst offset = computed(() =>\n isDeterminate.value\n ? circumference.value - (circumference.value * (props.value as number)) / 100\n : 0,\n)\n</script>\n\n<template>\n <div\n :class=\"classes\"\n role=\"progressbar\"\n :aria-valuemin=\"0\"\n :aria-valuemax=\"100\"\n :aria-valuenow=\"isDeterminate ? value : undefined\"\n v-bind=\"$attrs\"\n >\n <!-- Ring variant -->\n <template v-if=\"variant === 'ring'\">\n <svg\n :width=\"dim\"\n :height=\"dim\"\n :viewBox=\"`0 0 ${dim} ${dim}`\"\n class=\"strand-progress__ring\"\n >\n <circle\n :cx=\"dim / 2\"\n :cy=\"dim / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"RING_STROKE\"\n class=\"strand-progress__track\"\n />\n <circle\n :cx=\"dim / 2\"\n :cy=\"dim / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"RING_STROKE\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"isDeterminate ? offset : undefined\"\n stroke-linecap=\"round\"\n class=\"strand-progress__fill\"\n :transform=\"`rotate(-90 ${dim / 2} ${dim / 2})`\"\n />\n </svg>\n </template>\n <!-- Bar variant -->\n <template v-else>\n <div\n class=\"strand-progress__fill\"\n :style=\"isDeterminate ? { width: `${value}%` } : undefined\"\n />\n </template>\n </div>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Size of the spinner */\n size?: 'sm' | 'md' | 'lg'\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n className: '',\n})\n\nconst classes = computed(() =>\n [\n 'strand-spinner',\n `strand-spinner--${props.size}`,\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n</script>\n\n<template>\n <span :class=\"classes\" role=\"status\" v-bind=\"$attrs\">\n <span class=\"strand-spinner__ring\" aria-hidden=\"true\" />\n <span class=\"strand-spinner__sr-only\">Loading</span>\n </span>\n</template>\n","<!--! Strand Vue | MIT License | dillingerstaffing.com -->\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n /** Shape variant */\n variant?: 'text' | 'rectangle' | 'circle'\n /** CSS width value */\n width?: string\n /** CSS height value */\n height?: string\n /** Additional CSS class */\n className?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n className: '',\n})\n\nconst effectiveWidth = computed(() =>\n props.width ?? (props.variant === 'text' ? '100%' : undefined),\n)\n\nconst effectiveHeight = computed(() =>\n props.variant === 'circle' ? effectiveWidth.value : props.height,\n)\n\nconst classes = computed(() =>\n [\n 'strand-skeleton',\n `strand-skeleton--${props.variant}`,\n 'strand-skeleton--shimmer',\n props.className,\n ]\n .filter(Boolean)\n .join(' '),\n)\n\nconst inlineStyle = computed(() => ({\n width: effectiveWidth.value,\n height: effectiveHeight.value,\n}))\n</script>\n\n<template>\n <div :class=\"classes\" aria-hidden=\"true\" :style=\"inlineStyle\" v-bind=\"$attrs\" />\n</template>\n"],"names":["props","__props","emit","__emit","isDisabled","computed","classes","handleClick","event","_createElementBlock","_openBlock","_hoisted_2","_createElementVNode","_normalizeStyle","_renderSlot","_ctx","slots","useSlots","wrapperClasses","handleInput","target","$slots","_hoisted_1","_hoisted_3","textareaRef","ref","currentLength","resize","watch","onMounted","_toDisplayString","handleChange","_Fragment","_renderList","opt","inputRef","val","ariaChecked","handleKeyDown","_cache","_hoisted_4","_hoisted_5","_createTextVNode","_mergeProps","$attrs","hasChildren","displayValue","ariaLabel","badgeClasses","imgError","handleError","showImage","displayInitials","sortKey","sortDirection","handleSort","key","nextDirection","sortIndicator","col","$event","row","rowIndex","inlineStyle","isVertical","isLabeled","tablistRef","focusAndSelect","index","tab","buttons","_a","_b","currentIndex","t","nextIndex","_normalizeClass","item","menuOpen","toggleMenu","_hoisted_7","_hoisted_8","isUrgent","ToastKey","useToast","ctx","inject","toastIdCounter","toasts","timers","removeToast","id","timer","addToast","options","entry","provide","onUnmounted","status","role","handleDismiss","FOCUSABLE_SELECTOR","dialogIdCounter","titleId","panelRef","previousFocus","originalOverflow","panelClasses","panel","focusable","first","last","handleBackdropClick","isOpen","nextTick","tooltipIdCounter","tooltipId","visible","show","hide","tooltipClasses","RING_STROKE","RING_SIZES","isDeterminate","dim","radius","circumference","offset","effectiveWidth","effectiveHeight"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=test-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-setup.d.ts","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@dillingerstaffing/strand-vue",
3
+ "version": "0.4.0",
4
+ "description": "Strand UI - Vue 3 component library built on the Strand Design Language",
5
+ "author": "Dillinger Staffing <engineering@dillingerstaffing.com> (https://dillingerstaffing.com)",
6
+ "license": "MIT",
7
+ "keywords": ["design-system", "ui-components", "vue", "vue3", "css-custom-properties", "design-tokens", "accessibility", "wcag", "aria", "component-library"],
8
+ "homepage": "https://dillingerstaffing.com/labs/strand",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/dillingerstaffing/strand",
12
+ "directory": "packages/strand-vue"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/dillingerstaffing/strand/issues"
16
+ },
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
26
+ "./css/strand-ui.css": "./dist/css/strand-ui.css"
27
+ },
28
+ "style": "./dist/css/strand-ui.css",
29
+ "files": ["dist/", "src/"],
30
+ "sideEffects": ["dist/css/*.css"],
31
+ "scripts": {
32
+ "build": "vite build && cp ../../HTML_REFERENCE.md ./HTML_REFERENCE.md",
33
+ "test": "vitest run"
34
+ },
35
+ "peerDependencies": {
36
+ "vue": "^3.4.0"
37
+ },
38
+ "dependencies": {
39
+ "@dillingerstaffing/strand": "workspace:^"
40
+ },
41
+ "devDependencies": {
42
+ "@testing-library/jest-dom": "^6.6.0",
43
+ "@testing-library/vue": "^8.1.0",
44
+ "@vitejs/plugin-vue": "^5.0.0",
45
+ "jsdom": "^26.0.0",
46
+ "vite": "^6.0.0",
47
+ "vite-plugin-dts": "^4.0.0",
48
+ "vitest": "^3.0.0",
49
+ "vue": "^3.5.0"
50
+ }
51
+ }
@@ -0,0 +1,100 @@
1
+ /*! Strand Vue | MIT License | dillingerstaffing.com */
2
+
3
+ import { describe, it, expect } from 'vitest'
4
+ import { render, fireEvent } from '@testing-library/vue'
5
+ import Alert from './Alert.vue'
6
+
7
+ describe('Alert', () => {
8
+ it('renders with default props', () => {
9
+ const { container } = render(Alert, {
10
+ slots: { default: 'Information message' },
11
+ })
12
+ const alert = container.querySelector('.strand-alert')
13
+ expect(alert).toBeInTheDocument()
14
+ expect(alert).toHaveClass('strand-alert--info')
15
+ expect(alert).toHaveAttribute('role', 'status')
16
+ const content = container.querySelector('.strand-alert__content')
17
+ expect(content).toBeInTheDocument()
18
+ expect(content).toHaveTextContent('Information message')
19
+ })
20
+
21
+ it('applies status variant classes', () => {
22
+ const statuses = ['info', 'success', 'warning', 'error'] as const
23
+ for (const status of statuses) {
24
+ const { container } = render(Alert, {
25
+ props: { status },
26
+ slots: { default: 'Msg' },
27
+ })
28
+ expect(container.querySelector('.strand-alert')).toHaveClass(`strand-alert--${status}`)
29
+ }
30
+ })
31
+
32
+ it('sets role="alert" for error status', () => {
33
+ const { container } = render(Alert, {
34
+ props: { status: 'error' },
35
+ slots: { default: 'Error' },
36
+ })
37
+ expect(container.querySelector('.strand-alert')).toHaveAttribute('role', 'alert')
38
+ })
39
+
40
+ it('sets role="alert" for warning status', () => {
41
+ const { container } = render(Alert, {
42
+ props: { status: 'warning' },
43
+ slots: { default: 'Warning' },
44
+ })
45
+ expect(container.querySelector('.strand-alert')).toHaveAttribute('role', 'alert')
46
+ })
47
+
48
+ it('sets role="status" for info status', () => {
49
+ const { container } = render(Alert, {
50
+ props: { status: 'info' },
51
+ slots: { default: 'Info' },
52
+ })
53
+ expect(container.querySelector('.strand-alert')).toHaveAttribute('role', 'status')
54
+ })
55
+
56
+ it('sets role="status" for success status', () => {
57
+ const { container } = render(Alert, {
58
+ props: { status: 'success' },
59
+ slots: { default: 'Success' },
60
+ })
61
+ expect(container.querySelector('.strand-alert')).toHaveAttribute('role', 'status')
62
+ })
63
+
64
+ it('shows dismiss button when dismissible', () => {
65
+ const { container } = render(Alert, {
66
+ props: { dismissible: true },
67
+ slots: { default: 'Msg' },
68
+ })
69
+ const dismiss = container.querySelector('.strand-alert__dismiss')
70
+ expect(dismiss).toBeInTheDocument()
71
+ expect(dismiss).toHaveAttribute('type', 'button')
72
+ expect(dismiss).toHaveAttribute('aria-label', 'Dismiss')
73
+ })
74
+
75
+ it('does not show dismiss button by default', () => {
76
+ const { container } = render(Alert, {
77
+ slots: { default: 'Msg' },
78
+ })
79
+ expect(container.querySelector('.strand-alert__dismiss')).not.toBeInTheDocument()
80
+ })
81
+
82
+ it('emits dismiss event when dismiss button clicked', async () => {
83
+ const { container, emitted } = render(Alert, {
84
+ props: { dismissible: true },
85
+ slots: { default: 'Msg' },
86
+ })
87
+ const dismiss = container.querySelector('.strand-alert__dismiss') as HTMLButtonElement
88
+ await fireEvent.click(dismiss)
89
+ expect(emitted().dismiss).toHaveLength(1)
90
+ })
91
+
92
+ it('dismiss button contains close character', () => {
93
+ const { container } = render(Alert, {
94
+ props: { dismissible: true },
95
+ slots: { default: 'Msg' },
96
+ })
97
+ const dismiss = container.querySelector('.strand-alert__dismiss')
98
+ expect(dismiss).toHaveTextContent('\u00D7')
99
+ })
100
+ })
@@ -0,0 +1,54 @@
1
+ <!--! Strand Vue | MIT License | dillingerstaffing.com -->
2
+ <script setup lang="ts">
3
+ import { computed } from 'vue'
4
+
5
+ export interface AlertProps {
6
+ /** Visual status of the alert */
7
+ status?: 'info' | 'success' | 'warning' | 'error'
8
+ /** Show dismiss button */
9
+ dismissible?: boolean
10
+ }
11
+
12
+ const props = withDefaults(defineProps<AlertProps>(), {
13
+ status: 'info',
14
+ dismissible: false,
15
+ })
16
+
17
+ const emit = defineEmits<{
18
+ (e: 'dismiss'): void
19
+ }>()
20
+
21
+ const role = computed(() =>
22
+ props.status === 'error' || props.status === 'warning' ? 'alert' : 'status',
23
+ )
24
+
25
+ const classes = computed(() =>
26
+ [
27
+ 'strand-alert',
28
+ `strand-alert--${props.status}`,
29
+ ]
30
+ .filter(Boolean)
31
+ .join(' '),
32
+ )
33
+
34
+ function handleDismiss() {
35
+ emit('dismiss')
36
+ }
37
+ </script>
38
+
39
+ <template>
40
+ <div :class="classes" :role="role">
41
+ <div class="strand-alert__content">
42
+ <slot />
43
+ </div>
44
+ <button
45
+ v-if="dismissible"
46
+ type="button"
47
+ class="strand-alert__dismiss"
48
+ aria-label="Dismiss"
49
+ @click="handleDismiss"
50
+ >
51
+ &#215;
52
+ </button>
53
+ </div>
54
+ </template>
@@ -0,0 +1,2 @@
1
+ /*! Strand Vue | MIT License | dillingerstaffing.com */
2
+ export { default as Alert } from './Alert.vue'
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render } from '@testing-library/vue'
3
+ import Avatar from './Avatar.vue'
4
+
5
+ describe('Avatar', () => {
6
+ // ── Rendering ──
7
+
8
+ it('renders a div element', () => {
9
+ const { container } = render(Avatar)
10
+ expect(container.firstElementChild?.tagName).toBe('DIV')
11
+ })
12
+
13
+ it('has role img', () => {
14
+ const { container } = render(Avatar)
15
+ expect(container.firstElementChild?.getAttribute('role')).toBe('img')
16
+ })
17
+
18
+ // ── Image mode ──
19
+
20
+ it('renders an img when src is provided', () => {
21
+ const { container } = render(Avatar, {
22
+ props: { src: 'https://example.com/photo.jpg', alt: 'User' },
23
+ })
24
+ const img = container.querySelector('img')
25
+ expect(img).toBeTruthy()
26
+ expect(img?.getAttribute('src')).toBe('https://example.com/photo.jpg')
27
+ })
28
+
29
+ it('applies strand-avatar__img class to image', () => {
30
+ const { container } = render(Avatar, {
31
+ props: { src: 'https://example.com/photo.jpg' },
32
+ })
33
+ expect(container.querySelector('.strand-avatar__img')).toBeTruthy()
34
+ })
35
+
36
+ // ── Initials mode ──
37
+
38
+ it('renders initials when no src is provided', () => {
39
+ const { container } = render(Avatar, { props: { initials: 'AB' } })
40
+ const span = container.querySelector('.strand-avatar__initials')
41
+ expect(span).toBeTruthy()
42
+ expect(span?.textContent).toBe('AB')
43
+ })
44
+
45
+ it('truncates initials to 2 characters', () => {
46
+ const { container } = render(Avatar, { props: { initials: 'ABC' } })
47
+ const span = container.querySelector('.strand-avatar__initials')
48
+ expect(span?.textContent).toBe('AB')
49
+ })
50
+
51
+ it('uppercases initials', () => {
52
+ const { container } = render(Avatar, { props: { initials: 'ab' } })
53
+ const span = container.querySelector('.strand-avatar__initials')
54
+ expect(span?.textContent).toBe('AB')
55
+ })
56
+
57
+ it('marks initials as aria-hidden', () => {
58
+ const { container } = render(Avatar, { props: { initials: 'AB' } })
59
+ const span = container.querySelector('.strand-avatar__initials')
60
+ expect(span?.getAttribute('aria-hidden')).toBe('true')
61
+ })
62
+
63
+ // ── Sizes ──
64
+
65
+ it('applies md size class by default', () => {
66
+ const { container } = render(Avatar)
67
+ expect(container.firstElementChild?.className).toContain('strand-avatar--md')
68
+ })
69
+
70
+ it('applies sm size class', () => {
71
+ const { container } = render(Avatar, { props: { size: 'sm' } })
72
+ expect(container.firstElementChild?.className).toContain('strand-avatar--sm')
73
+ })
74
+
75
+ it('applies lg size class', () => {
76
+ const { container } = render(Avatar, { props: { size: 'lg' } })
77
+ expect(container.firstElementChild?.className).toContain('strand-avatar--lg')
78
+ })
79
+
80
+ it('applies xl size class', () => {
81
+ const { container } = render(Avatar, { props: { size: 'xl' } })
82
+ expect(container.firstElementChild?.className).toContain('strand-avatar--xl')
83
+ })
84
+
85
+ // ── Accessibility ──
86
+
87
+ it('uses alt as aria-label when provided', () => {
88
+ const { container } = render(Avatar, { props: { alt: 'Jane Doe' } })
89
+ expect(container.firstElementChild?.getAttribute('aria-label')).toBe('Jane Doe')
90
+ })
91
+
92
+ it('uses initials as aria-label fallback', () => {
93
+ const { container } = render(Avatar, { props: { initials: 'jd' } })
94
+ expect(container.firstElementChild?.getAttribute('aria-label')).toBe('JD')
95
+ })
96
+
97
+ // ── Custom className ──
98
+
99
+ it('merges custom className', () => {
100
+ const { container } = render(Avatar, { props: { className: 'custom' } })
101
+ const el = container.firstElementChild
102
+ expect(el?.className).toContain('strand-avatar')
103
+ expect(el?.className).toContain('custom')
104
+ })
105
+ })
@@ -0,0 +1,56 @@
1
+ <!--! Strand Vue | MIT License | dillingerstaffing.com -->
2
+ <script setup lang="ts">
3
+ import { computed, ref } from 'vue'
4
+
5
+ interface Props {
6
+ /** Image URL */
7
+ src?: string
8
+ /** Alt text for image */
9
+ alt?: string
10
+ /** Fallback initials (1-2 characters) */
11
+ initials?: string
12
+ /** Avatar size */
13
+ size?: 'sm' | 'md' | 'lg' | 'xl'
14
+ /** Additional CSS class */
15
+ className?: string
16
+ }
17
+
18
+ const props = withDefaults(defineProps<Props>(), {
19
+ alt: '',
20
+ initials: '',
21
+ size: 'md',
22
+ className: '',
23
+ })
24
+
25
+ const imgError = ref(false)
26
+
27
+ const handleError = () => {
28
+ imgError.value = true
29
+ }
30
+
31
+ const showImage = computed(() => props.src && !imgError.value)
32
+ const displayInitials = computed(() => props.initials.slice(0, 2).toUpperCase())
33
+
34
+ const classes = computed(() =>
35
+ [
36
+ 'strand-avatar',
37
+ `strand-avatar--${props.size}`,
38
+ props.className,
39
+ ]
40
+ .filter(Boolean)
41
+ .join(' '),
42
+ )
43
+ </script>
44
+
45
+ <template>
46
+ <div :class="classes" role="img" :aria-label="alt || displayInitials" v-bind="$attrs">
47
+ <img
48
+ v-if="showImage"
49
+ class="strand-avatar__img"
50
+ :src="src"
51
+ :alt="alt"
52
+ @error="handleError"
53
+ />
54
+ <span v-else class="strand-avatar__initials" aria-hidden="true">{{ displayInitials }}</span>
55
+ </div>
56
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Avatar } from './Avatar.vue'