@bluerobotics/bluevue 0.1.1 → 0.2.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 (91) hide show
  1. package/README.md +97 -18
  2. package/bin/create-blueos-extension.mjs +72 -0
  3. package/dist/bluevue.js +2464 -778
  4. package/dist/components/BlueApp.vue.d.ts +50 -0
  5. package/dist/components/BlueBanner.vue.d.ts +49 -0
  6. package/dist/components/BlueBannerGroup.vue.d.ts +20 -0
  7. package/dist/components/BlueButton.vue.d.ts +59 -0
  8. package/dist/components/BlueCard.vue.d.ts +45 -0
  9. package/dist/components/BlueCheckbox.vue.d.ts +34 -0
  10. package/dist/components/BlueChip.vue.d.ts +47 -0
  11. package/dist/components/BlueConfirmDialog.vue.d.ts +193 -0
  12. package/dist/components/BlueDialog.vue.d.ts +60 -0
  13. package/dist/components/BlueFileDrop.vue.d.ts +32 -0
  14. package/dist/components/BlueIcon.vue.d.ts +26 -0
  15. package/dist/components/BlueInput.vue.d.ts +1 -0
  16. package/dist/components/BlueProgressBar.vue.d.ts +28 -0
  17. package/dist/components/BlueRadioGroup.vue.d.ts +40 -0
  18. package/dist/components/BlueSection.vue.d.ts +36 -0
  19. package/dist/components/BlueSnackbar.vue.d.ts +19 -0
  20. package/dist/components/BlueSpinner.vue.d.ts +25 -0
  21. package/dist/components/BlueStat.vue.d.ts +21 -0
  22. package/dist/components/BlueStepsDialog.vue.d.ts +62 -0
  23. package/dist/components/BlueTable.vue.d.ts +62 -0
  24. package/dist/components/BlueTabs.vue.d.ts +37 -0
  25. package/dist/components/BlueTextarea.vue.d.ts +40 -0
  26. package/dist/components/BlueTooltip.vue.d.ts +44 -0
  27. package/dist/components/BlueWindRose.vue.d.ts +37 -0
  28. package/dist/composables/useBlueLoading.d.ts +22 -0
  29. package/dist/composables/useBlueOs.d.ts +39 -0
  30. package/dist/composables/useBlueSnackbar.d.ts +30 -0
  31. package/dist/index.d.ts +30 -0
  32. package/dist/services/blueos.d.ts +37 -0
  33. package/dist/style.css +1 -1
  34. package/dist/types/banner.d.ts +16 -0
  35. package/dist/utils/files.d.ts +13 -0
  36. package/dist/utils/theme.d.ts +22 -0
  37. package/package.json +8 -2
  38. package/src/components/BlueApp.vue +84 -0
  39. package/src/components/BlueBanner.vue +196 -0
  40. package/src/components/BlueBannerGroup.vue +47 -0
  41. package/src/components/BlueButton.vue +134 -0
  42. package/src/components/BlueButtonGroup.vue +26 -24
  43. package/src/components/BlueCard.vue +62 -0
  44. package/src/components/BlueCheckbox.vue +100 -0
  45. package/src/components/BlueChip.vue +77 -0
  46. package/src/components/BlueConfirmDialog.vue +73 -0
  47. package/src/components/BlueDialog.vue +135 -0
  48. package/src/components/BlueFileDrop.vue +94 -0
  49. package/src/components/BlueIcon.vue +37 -0
  50. package/src/components/BlueInput.vue +21 -11
  51. package/src/components/BlueLoadingDialog.vue +22 -53
  52. package/src/components/BlueProgressBar.vue +46 -0
  53. package/src/components/BluePromptDialog.vue +51 -99
  54. package/src/components/BlueRadioGroup.vue +104 -0
  55. package/src/components/BlueSection.vue +33 -0
  56. package/src/components/BlueSelect.vue +12 -10
  57. package/src/components/BlueSlider.vue +1 -3
  58. package/src/components/BlueSnackbar.vue +79 -0
  59. package/src/components/BlueSpinner.vue +37 -0
  60. package/src/components/BlueStat.vue +26 -0
  61. package/src/components/BlueStepsDialog.vue +180 -0
  62. package/src/components/BlueSwitch.vue +16 -13
  63. package/src/components/BlueTable.vue +154 -0
  64. package/src/components/BlueTabs.vue +77 -0
  65. package/src/components/BlueTextarea.vue +101 -0
  66. package/src/components/BlueTooltip.vue +88 -0
  67. package/src/components/BlueWindRose.vue +267 -0
  68. package/src/composables/useBlueLoading.ts +38 -0
  69. package/src/composables/useBlueOs.ts +154 -0
  70. package/src/composables/useBlueSnackbar.ts +89 -0
  71. package/src/index.ts +40 -0
  72. package/src/services/blueos.ts +95 -0
  73. package/src/styles/bluevue.css +136 -0
  74. package/src/types/banner.ts +17 -0
  75. package/src/utils/files.ts +44 -0
  76. package/src/utils/theme.ts +37 -0
  77. package/templates/extension/.github/workflows/deploy.yml +23 -0
  78. package/templates/extension/Dockerfile +56 -0
  79. package/templates/extension/README.md +35 -0
  80. package/templates/extension/backend/main.py +45 -0
  81. package/templates/extension/backend/requirements.txt +2 -0
  82. package/templates/extension/frontend/.eslintrc.cjs +20 -0
  83. package/templates/extension/frontend/env.d.ts +7 -0
  84. package/templates/extension/frontend/index.html +12 -0
  85. package/templates/extension/frontend/package.json +30 -0
  86. package/templates/extension/frontend/src/App.vue +51 -0
  87. package/templates/extension/frontend/src/main.ts +8 -0
  88. package/templates/extension/frontend/src/styles/global.css +11 -0
  89. package/templates/extension/frontend/tsconfig.json +25 -0
  90. package/templates/extension/frontend/tsconfig.node.json +11 -0
  91. package/templates/extension/frontend/vite.config.ts +24 -0
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--color-gray-100:oklch(96.7% .003 264.542);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-700:oklch(37.3% .034 259.733);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--font-weight-medium:500;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-lg:.5rem;--ease-out:cubic-bezier(0,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-\[18\%\]{inset-inline:18%}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing)*3)}.top-\[4px\]{top:4px}.right-0{right:calc(var(--spacing)*0)}.right-3{right:calc(var(--spacing)*3)}.right-\[5px\]{right:5px}.bottom-1\/2{bottom:50%}.bottom-\[4px\]{bottom:4px}.bottom-full{bottom:100%}.left-1\/2{left:50%}.left-\[-20px\]{left:-20px}.left-\[5px\]{left:5px}.z-10{z-index:10}.z-50{z-index:50}.z-\[20\]{z-index:20}.z-\[666\]{z-index:666}.z-\[1000\]{z-index:1000}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.my-6{margin-block:calc(var(--spacing)*6)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-\[2px\]{margin-top:2px}.mt-\[15px\]{margin-top:15px}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-6{margin-right:calc(var(--spacing)*6)}.mr-\[2px\]{margin-right:2px}.mr-\[3px\]{margin-right:3px}.-mb-\[1px\]{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing)*2)}.h-3\/4{height:75%}.h-7{height:calc(var(--spacing)*7)}.h-\[100px\]{height:100px}.h-full{height:100%}.h-px{height:1px}.max-h-\[60vh\]{max-height:60vh}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-\[1px\]{width:1px}.w-\[6px\]{width:6px}.w-\[100px\]{width:100px}.w-\[320px\]{width:320px}.w-\[624px\]{width:624px}.w-\[760px\]{width:760px}.w-\[calc\(50\%-4px\)\]{width:calc(50% - 4px)}.w-full{width:100%}.w-max{width:max-content}.max-w-\[45\%\]{max-width:45%}.max-w-\[150px\]{max-width:150px}.max-w-\[280px\]{max-width:280px}.max-w-full{max-width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[30px\]{min-width:30px}.min-w-\[60px\]{min-width:60px}.min-w-\[90px\]{min-width:90px}.min-w-\[140px\]{min-width:140px}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.shrink-\[1000\]{flex-shrink:1000}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[0\.22em\]{--tw-translate-y:.22em;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-5{gap:calc(var(--spacing)*5)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[6px\]{border-radius:6px}.rounded-\[8px\]{border-radius:8px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1px\]{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-solid{--tw-border-style:solid;border-style:solid}.border-\[\#555555\]{border-color:#555}.border-\[\#FFFFFF22\]{border-color:#fff2}.border-\[\#dddddd\]{border-color:#ddd}.border-\[\#ffffff0d\]{border-color:#ffffff0d}.border-\[\#ffffff1a\]{border-color:#ffffff1a}.border-\[var\(--bluevue-error\)\]{border-color:var(--bluevue-error)}.border-gray-300{border-color:var(--color-gray-300)}.border-white{border-color:var(--color-white)}.bg-\[\#2d2d2d\]{background-color:#2d2d2d}.bg-\[\#00000011\]{background-color:#0001}.bg-\[\#00000033\]{background-color:#0003}.bg-\[\#6699cc\]{background-color:#69c}.bg-\[\#132029\]{background-color:#132029}.bg-\[\#464646AA\]{background-color:#464646aa}.bg-\[\#FFFFFF22\]{background-color:#fff2}.bg-\[\#ffffff0d\]{background-color:#ffffff0d}.bg-\[\#ffffff11\]{background-color:#fff1}.bg-\[\#ffffff22\]{background-color:#fff2}.bg-\[var\(--bluevue-primary\)\]{background-color:var(--bluevue-primary)}.bg-\[var\(--bluevue-surface\)\]{background-color:var(--bluevue-surface)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.p-6{padding:calc(var(--spacing)*6)}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-13{padding-inline:calc(var(--spacing)*13)}.px-\[6px\]{padding-inline:6px}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-7{padding-top:calc(var(--spacing)*7)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-4{padding-right:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.text-start{text-align:start}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[16px\]{font-size:16px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[22px\]{font-size:22px}.text-\[24px\]{font-size:24px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.text-\[\#42A5F5\]{color:#42a5f5}.text-\[\#00000066\]{color:#0006}.text-\[\#FFFFFF99\]{color:#fff9}.text-\[\#ff6b6b\]{color:#ff6b6b}.text-\[\#ffffff44\]{color:#fff4}.text-\[\#ffffff66\]{color:#fff6}.text-\[\#ffffff88\]{color:#fff8}.text-\[\#ffffff99\]{color:#fff9}.text-\[\#ffffffaa\]{color:#fffa}.text-\[var\(--bluevue-error\)\]{color:var(--bluevue-error)}.text-black{color:var(--color-black)}.text-gray-500{color:var(--color-gray-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}}.placeholder\:text-\[\#00000055\]::placeholder{color:#0005}.placeholder\:text-\[\#ffffff44\]::placeholder{color:#fff4}@media (hover:hover){.hover\:bg-\[\#333333\]:hover{background-color:#333}.hover\:bg-\[\#ffffff11\]:hover{background-color:#fff1}.hover\:bg-\[\#ffffff22\]:hover{background-color:#fff2}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:text-white:hover{color:var(--color-white)}.hover\:brightness-125:hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-\[\#9e9e9e80\]:disabled{background-color:#9e9e9e80}.disabled\:text-\[\#afafaf\]:disabled{color:#afafaf}.disabled\:opacity-30:disabled{opacity:.3}@media (hover:hover){.disabled\:hover\:brightness-100:disabled:hover{--tw-brightness:brightness(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}}:root{--bluevue-primary:#0b5087;--bluevue-error:#cf6679;--bluevue-surface:#363636;--bluevue-accent:#69c;--bluevue-panel-bg:#1e1e1ef5;--bluevue-hairline:#ffffff14;--bluevue-elevation-1:0 2px 1px -1px #0003,0 1px 1px 0 #00000024,0 1px 3px 0 #0000001f;--bluevue-elevation-5:0 3px 5px -1px #0003,0 5px 8px 0 #00000024,0 1px 14px 0 #0000001f;--bluevue-inset-1:inset 0 1px 2px #00000056,inset 0 .5px .5px #0000003a,inset 0 -.5px 0 #ffffff0b}.bluevue-elevation-1{box-shadow:var(--bluevue-elevation-1)}.bluevue-elevation-5{box-shadow:var(--bluevue-elevation-5)}.bluevue-inset-1{box-shadow:var(--bluevue-inset-1)}.bluevue-panel{background-color:var(--bluevue-panel-bg);-webkit-backdrop-filter:blur(8px);border:1px solid var(--bluevue-hairline);box-shadow:0 4px 4px #0003,0 8px 12px 6px #00000026}.bluevue-dialog{background:0 0;border:none;max-width:min(92vw,624px);max-height:92vh;margin:auto;padding:0;overflow:visible}.bluevue-dialog:focus,.bluevue-dialog:focus-visible,.bluevue-popover:focus,.bluevue-popover:focus-visible{outline:none}.bluevue-dialog::backdrop{-webkit-backdrop-filter:blur(8px);background:#000000e0}.bluevue-popover{background:0 0;border:none;margin:0;padding:0;position:fixed;overflow:visible}.bluevue-expand-enter-active,.bluevue-expand-leave-active{transition:max-height .3s;overflow:hidden}.bluevue-expand-enter-from,.bluevue-expand-leave-to{max-height:0}.bluevue-expand-enter-to,.bluevue-expand-leave-from{max-height:500px}.bluevue-loading__icon{animation:4s infinite bluevue-prop}@keyframes bluevue-prop{0%{animation-timing-function:cubic-bezier(.45,0,.55,1);transform:rotate(0)}50%{animation-timing-function:cubic-bezier(.35,0,.45,1);transform:rotate(80deg)}to{transform:rotate(1440deg)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-100:oklch(96.7% .003 264.542);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-500:oklch(55.1% .027 264.364);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-medium:500;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-lg:.5rem;--ease-out:cubic-bezier(0,0,.2,1);--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-x-\[18\%\]{inset-inline:18%}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing)*3)}.top-4{top:calc(var(--spacing)*4)}.top-\[4px\]{top:4px}.right-3{right:calc(var(--spacing)*3)}.right-4{right:calc(var(--spacing)*4)}.right-\[5px\]{right:5px}.-bottom-px{bottom:-1px}.bottom-1\/2{bottom:50%}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-\[4px\]{bottom:4px}.left-4{left:calc(var(--spacing)*4)}.left-\[-20px\]{left:-20px}.left-\[5px\]{left:5px}.z-10{z-index:10}.z-50{z-index:50}.z-\[1\]{z-index:1}.z-\[20\]{z-index:20}.z-\[666\]{z-index:666}.z-\[2000\]{z-index:2000}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.my-6{margin-block:calc(var(--spacing)*6)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-\[1px\]{margin-top:1px}.mt-\[2px\]{margin-top:2px}.mt-\[15px\]{margin-top:15px}.-mr-1{margin-right:calc(var(--spacing)*-1)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-6{margin-right:calc(var(--spacing)*6)}.mr-\[2px\]{margin-right:2px}.mr-\[3px\]{margin-right:3px}.-mb-\[1px\]{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-0{height:calc(var(--spacing)*0)}.h-2{height:calc(var(--spacing)*2)}.h-3\/4{height:75%}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-\[2px\]{height:2px}.h-\[8px\]{height:8px}.h-\[16px\]{height:16px}.h-\[18px\]{height:18px}.h-\[22px\]{height:22px}.h-\[100px\]{height:100px}.h-full{height:100%}.h-px{height:1px}.max-h-\[60vh\]{max-height:60vh}.max-h-\[220px\]{max-height:220px}.\!min-h-full{min-height:100%!important}.min-h-screen{min-height:100vh}.\!w-auto{width:auto!important}.w-0{width:calc(var(--spacing)*0)}.w-1\/2{width:50%}.w-2{width:calc(var(--spacing)*2)}.w-6{width:calc(var(--spacing)*6)}.w-7{width:calc(var(--spacing)*7)}.w-\[1px\]{width:1px}.w-\[6px\]{width:6px}.w-\[8px\]{width:8px}.w-\[16px\]{width:16px}.w-\[18px\]{width:18px}.w-\[84px\]{width:84px}.w-\[100px\]{width:100px}.w-\[170px\]{width:170px}.w-\[190px\]{width:190px}.w-\[320px\]{width:320px}.w-\[340px\]{width:340px}.w-\[420px\]{width:420px}.w-\[440px\]{width:440px}.w-\[560px\]{width:560px}.w-\[calc\(50\%-4px\)\]{width:calc(50% - 4px)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-\[45\%\]{max-width:45%}.max-w-\[150px\]{max-width:150px}.max-w-\[280px\]{max-width:280px}.max-w-\[640px\]{max-width:640px}.max-w-\[680px\]{max-width:680px}.max-w-\[900px\]{max-width:900px}.max-w-\[min\(90vw\,420px\)\]{max-width:min(90vw,420px)}.max-w-full{max-width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[30px\]{min-width:30px}.min-w-\[60px\]{min-width:60px}.min-w-\[90px\]{min-width:90px}.min-w-\[120px\]{min-width:120px}.min-w-\[140px\]{min-width:140px}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.shrink-\[1000\]{flex-shrink:1000}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[0\.22em\]{--tw-translate-y:.22em;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-context-menu{cursor:context-menu}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.resize-y{resize:vertical}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}.gap-10{gap:calc(var(--spacing)*10)}.gap-x-5{column-gap:calc(var(--spacing)*5)}.gap-y-2{row-gap:calc(var(--spacing)*2)}.self-center{align-self:center}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-\[6px\]{border-radius:6px}.rounded-\[8px\]{border-radius:8px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-t-\[8px\]{border-top-left-radius:8px;border-top-right-radius:8px}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1px\]{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-solid{--tw-border-style:solid;border-style:solid}.border-\[\#0000000a\]{border-color:#0000000a}.border-\[\#00000014\]{border-color:#00000014}.border-\[\#00000033\]{border-color:#0003}.border-\[\#00000044\]{border-color:#0004}.border-\[\#555555\]{border-color:#555}.border-\[\#FFFFFF22\]{border-color:#fff2}.border-\[\#dddddd\]{border-color:#ddd}.border-\[\#ffffff0a\]{border-color:#ffffff0a}.border-\[\#ffffff0d\]{border-color:#ffffff0d}.border-\[\#ffffff1a\]{border-color:#ffffff1a}.border-\[\#ffffff14\]{border-color:#ffffff14}.border-\[\#ffffff33\]{border-color:#fff3}.border-\[\#ffffff44\]{border-color:#fff4}.border-\[var\(--bluevue-accent\)\]{border-color:var(--bluevue-accent)}.border-\[var\(--bluevue-error\)\]{border-color:var(--bluevue-error)}.border-\[var\(--bluevue-primary\)\]{border-color:var(--bluevue-primary)}.border-gray-300{border-color:var(--color-gray-300)}.border-transparent{border-color:#0000}.border-white{border-color:var(--color-white)}.bg-\[\#0C577B\]{background-color:#0c577b}.bg-\[\#1e1e1ecc\]{background-color:#1e1e1ecc}.bg-\[\#2d2d2d\]{background-color:#2d2d2d}.bg-\[\#00000006\]{background-color:#00000006}.bg-\[\#00000008\]{background-color:#00000008}.bg-\[\#9e9e9e80\]{background-color:#9e9e9e80}.bg-\[\#00000011\]{background-color:#0001}.bg-\[\#00000022\]{background-color:#0002}.bg-\[\#00000033\]{background-color:#0003}.bg-\[\#00000044\]{background-color:#0004}.bg-\[\#6699cc\]{background-color:#69c}.bg-\[\#333333f2\]{background-color:#333333f2}.bg-\[\#464646AA\]{background-color:#464646aa}.bg-\[\#15151577\]{background-color:#15151577}.bg-\[\#FFFFFF22\]{background-color:#fff2}.bg-\[\#f5f5f5cc\]{background-color:#f5f5f5cc}.bg-\[\#ffffff0d\]{background-color:#ffffff0d}.bg-\[\#ffffff1a\]{background-color:#ffffff1a}.bg-\[\#ffffff06\]{background-color:#ffffff06}.bg-\[\#ffffff08\]{background-color:#ffffff08}.bg-\[\#ffffff11\]{background-color:#fff1}.bg-\[\#ffffff22\]{background-color:#fff2}.bg-\[var\(--bluevue-accent\)\],.bg-\[var\(--bluevue-accent\)\]\/10{background-color:var(--bluevue-accent)}@supports (color:color-mix(in lab, red, red)){.bg-\[var\(--bluevue-accent\)\]\/10{background-color:color-mix(in oklab,var(--bluevue-accent)10%,transparent)}}.bg-\[var\(--bluevue-primary\)\]{background-color:var(--bluevue-primary)}.bg-\[var\(--bluevue-surface\)\]{background-color:var(--bluevue-surface)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#0C577B\]{--tw-gradient-from:#0c577b;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-\[\#07293C\]{--tw-gradient-to:#07293c;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.p-3{padding:calc(var(--spacing)*3)}.p-\[2px\]{padding:2px}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-13{padding-inline:calc(var(--spacing)*13)}.px-\[9px\]{padding-inline:9px}.px-\[10px\]{padding-inline:10px}.px-\[11px\]{padding-inline:11px}.px-\[15px\]{padding-inline:15px}.px-\[19px\]{padding-inline:19px}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-10{padding-block:calc(var(--spacing)*10)}.py-\[2px\]{padding-block:2px}.py-\[6px\]{padding-block:6px}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-7{padding-top:calc(var(--spacing)*7)}.pt-\[6px\]{padding-top:6px}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-4{padding-right:calc(var(--spacing)*4)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-end{text-align:end}.text-left{text-align:left}.text-start{text-align:start}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[24px\]{font-size:24px}.text-\[26px\]{font-size:26px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-\[0\.14em\]{--tw-tracking:.14em;letter-spacing:.14em}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.text-\[\#000000cc\]{color:#000c}.text-\[\#8FD3F4\]{color:#8fd3f4}.text-\[\#00000066\]{color:#0006}.text-\[\#66BB6A\]{color:#66bb6a}.text-\[\#00000088\]{color:#0008}.text-\[\#00000099\]{color:#0009}.text-\[\#D6E9F5\]{color:#d6e9f5}.text-\[\#FFFFFF99\]{color:#fff9}.text-\[\#afafaf\]{color:#afafaf}.text-\[\#ff6b6b\]{color:#ff6b6b}.text-\[\#ffffff44\]{color:#fff4}.text-\[\#ffffff66\]{color:#fff6}.text-\[\#ffffff77\]{color:#fff7}.text-\[\#ffffff88\]{color:#fff8}.text-\[\#ffffff99\]{color:#fff9}.text-\[\#ffffffaa\]{color:#fffa}.text-\[\#ffffffcc\]{color:#fffc}.text-\[var\(--bluevue-error\)\]{color:var(--bluevue-error)}.text-black{color:var(--color-black)}.text-gray-500{color:var(--color-gray-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-dotted{text-decoration-style:dotted}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-\[var\(--bluevue-error\)\]{outline-color:var(--bluevue-error)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.peer-focus-visible\:outline:is(:where(.peer):focus-visible~*){outline-style:var(--tw-outline-style);outline-width:1px}.peer-focus-visible\:outline-2:is(:where(.peer):focus-visible~*){outline-style:var(--tw-outline-style);outline-width:2px}.peer-focus-visible\:outline-offset-2:is(:where(.peer):focus-visible~*){outline-offset:2px}.peer-focus-visible\:outline-\[var\(--bluevue-accent\)\]:is(:where(.peer):focus-visible~*){outline-color:var(--bluevue-accent)}.placeholder\:text-\[\#00000055\]::placeholder{color:#0005}.placeholder\:text-\[\#ffffff44\]::placeholder{color:#fff4}.placeholder\:text-\[\#ffffff55\]::placeholder{color:#fff5}@media (hover:hover){.hover\:bg-\[\#0000000a\]:hover{background-color:#0000000a}.hover\:bg-\[\#00000006\]:hover{background-color:#00000006}.hover\:bg-\[\#00000011\]:hover{background-color:#0001}.hover\:bg-\[\#00000022\]:hover{background-color:#0002}.hover\:bg-\[\#333333\]:hover{background-color:#333}.hover\:bg-\[\#ffffff0a\]:hover{background-color:#ffffff0a}.hover\:bg-\[\#ffffff08\]:hover{background-color:#ffffff08}.hover\:bg-\[\#ffffff11\]:hover{background-color:#fff1}.hover\:bg-\[\#ffffff14\]:hover{background-color:#ffffff14}.hover\:bg-\[\#ffffff22\]:hover{background-color:#fff2}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-white:hover{color:var(--color-white)}.hover\:opacity-100:hover{opacity:1}.hover\:brightness-125:hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.focus\:outline:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-2:focus{outline-style:var(--tw-outline-style);outline-width:2px}.focus\:outline-offset-0:focus{outline-offset:0px}.focus\:outline-\[var\(--bluevue-accent\)\]:focus{outline-color:var(--bluevue-accent)}@media (min-width:40rem){.sm\:inline{display:inline}}}:root{--bluevue-primary:#0b5087;--bluevue-error:#cf6679;--bluevue-surface:#363636;--bluevue-accent:#69c;--bluevue-panel-bg:#1e1e1ef5;--bluevue-hairline:#ffffff14;--bluevue-elevation-1:0 2px 1px -1px #0003,0 1px 1px 0 #00000024,0 1px 3px 0 #0000001f;--bluevue-elevation-5:0 3px 5px -1px #0003,0 5px 8px 0 #00000024,0 1px 14px 0 #0000001f;--bluevue-inset-1:inset 0 1px 2px #00000056,inset 0 .5px .5px #0000003a,inset 0 -.5px 0 #ffffff0b;--bluevue-elevation-1-soft:0 2px 1px -1px #0000001a,0 1px 1px 0 #00000012,0 1px 3px 0 #0000000f}.bluevue-elevation-1{box-shadow:var(--bluevue-elevation-1)}.bluevue-elevation-5{box-shadow:var(--bluevue-elevation-5)}.bluevue-inset-1{box-shadow:var(--bluevue-inset-1)}.bluevue-elevation-1-soft{box-shadow:var(--bluevue-elevation-1-soft)}.bluevue-panel{background-color:var(--bluevue-panel-bg);-webkit-backdrop-filter:blur(8px);border:1px solid var(--bluevue-hairline);box-shadow:0 4px 4px #0003,0 8px 12px 6px #00000026}.bluevue-dialog{background:0 0;border:none;max-width:min(92vw,624px);max-height:92vh;margin:auto;padding:0;overflow:visible}.bluevue-dialog:focus,.bluevue-dialog:focus-visible,.bluevue-popover:focus,.bluevue-popover:focus-visible{outline:none}.bluevue-dialog::backdrop{-webkit-backdrop-filter:blur(8px);background:#000000e0}.bluevue-popover{background:0 0;border:none;margin:0;padding:0;position:fixed;overflow:visible}.mdi.bluevue-icon{flex-shrink:0;justify-content:center;align-items:center;line-height:1;display:inline-flex}.bluevue-icon--spin{animation:1.2s linear infinite bluevue-spin}.bluevue-spinner{box-sizing:border-box;border-style:solid;border-color:#ffffff2e;border-top-color:var(--bluevue-accent);border-radius:9999px;flex-shrink:0;animation:.8s linear infinite bluevue-spin;display:inline-block}@keyframes bluevue-spin{to{transform:rotate(360deg)}}.bluevue-progress__sweep{width:40%;animation:1.4s ease-in-out infinite bluevue-progress-sweep}@keyframes bluevue-progress-sweep{0%{transform:translate(-100%)}to{transform:translate(250%)}}@media (prefers-reduced-motion:reduce){.bluevue-icon--spin,.bluevue-spinner,.bluevue-progress__sweep{animation-duration:3s}}.bluevue-banner{box-sizing:border-box;height:26px;padding:4px;font-size:12px;line-height:16px;overflow:hidden}.bluevue-banner__icon{justify-content:center;align-items:center;width:16px;height:16px;font-size:16px;line-height:16px;display:flex}.bluevue-banner__text{text-overflow:ellipsis;white-space:nowrap;padding:0 8px;overflow:hidden}.bluevue-banner__reveal{transition:width .26s ease-in-out}.bluevue-banner--measuring .bluevue-banner__reveal{transition:none}@media (prefers-reduced-motion:reduce){.bluevue-banner__reveal{transition:none}}.bluevue-notice-enter-active,.bluevue-notice-leave-active,.bluevue-notice-move{transition:opacity .18s,transform .18s}.bluevue-notice-enter-from,.bluevue-notice-leave-to{opacity:0;transform:translateY(8px)}.bluevue-notice-leave-active{position:absolute}.bluevue-expand-enter-active,.bluevue-expand-leave-active{transition:max-height .3s;overflow:hidden}.bluevue-expand-enter-from,.bluevue-expand-leave-to{max-height:0}.bluevue-expand-enter-to,.bluevue-expand-leave-from{max-height:500px}.bluevue-loading__icon{animation:4s infinite bluevue-prop}@keyframes bluevue-prop{0%{animation-timing-function:cubic-bezier(.45,0,.55,1);transform:rotate(0)}50%{animation-timing-function:cubic-bezier(.35,0,.45,1);transform:rotate(80deg)}to{transform:rotate(1440deg)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -0,0 +1,16 @@
1
+ export type BannerSeverity = 'error' | 'warning' | 'info' | 'success';
2
+ /** A banner's message and look, which is all a group needs to lay one out. */
3
+ export interface BannerContent {
4
+ /** The message, which the banner is only as wide as while it is open. */
5
+ text: string;
6
+ /** Which of the standard looks to take (default 'info'). */
7
+ severity?: BannerSeverity;
8
+ /** An mdi class, in place of the severity's icon. */
9
+ icon?: string;
10
+ /** Icon and text colour, in place of the severity's. */
11
+ color?: string;
12
+ /** Fill, in place of the severity's. */
13
+ background?: string;
14
+ /** Hairline, in place of the severity's. */
15
+ border?: string;
16
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Saves an object as a JSON file the browser downloads. What an extension's "export configuration"
3
+ * button does.
4
+ * @param {unknown} data Anything JSON can hold.
5
+ * @param {string} filename Suggested name. '.json' is appended when it is missing.
6
+ */
7
+ export declare function downloadJson(data: unknown, filename: string): void;
8
+ /**
9
+ * Asks for a JSON file and parses it. Resolves to null when the picker is closed without a choice.
10
+ * @param {string} accept File types to offer (default '.json,application/json').
11
+ * @returns {Promise<T | null>} The parsed contents, or null. A malformed file rejects.
12
+ */
13
+ export declare function pickJsonFile<T = unknown>(accept?: string): Promise<T | null>;
@@ -0,0 +1,22 @@
1
+ /** Every token the components read, in the names they are written with. */
2
+ export interface BlueTheme {
3
+ /** Fills a committing button, a selected segment, a slider's pill. */
4
+ primary?: string;
5
+ /** The lighter blue used where the primary would disappear into a dark inset, such as a focus ring. */
6
+ accent?: string;
7
+ /** Anything that went wrong, or is about to. */
8
+ error?: string;
9
+ /** The card an extension's page is laid out on. */
10
+ surface?: string;
11
+ /** The frosted panel dialogs and menus are drawn on. */
12
+ panelBg?: string;
13
+ /** The line dividing one part of a panel from the next. */
14
+ hairline?: string;
15
+ }
16
+ /**
17
+ * Repaints the kit in an extension's own colours, or in the colours BlueOS reports for the vehicle.
18
+ * Only the tokens given are touched, so the rest keep the values the stylesheet ships with.
19
+ * @param {BlueTheme} theme The tokens to change.
20
+ * @param {HTMLElement} target Where to set them (default the document root, so they reach everything).
21
+ */
22
+ export declare function applyBlueTheme(theme: BlueTheme, target?: HTMLElement): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluerobotics/bluevue",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Blue Robotics Vue components for common UI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,8 +21,13 @@
21
21
  ],
22
22
  "files": [
23
23
  "dist",
24
- "src"
24
+ "src",
25
+ "bin",
26
+ "templates"
25
27
  ],
28
+ "bin": {
29
+ "create-blueos-extension": "./bin/create-blueos-extension.mjs"
30
+ },
26
31
  "exports": {
27
32
  ".": {
28
33
  "types": "./dist/index.d.ts",
@@ -35,6 +40,7 @@
35
40
  "dev": "vite",
36
41
  "build": "npm run typecheck && vite build && npm run build:css",
37
42
  "build:css": "tailwindcss --input src/styles/bundled.css --output dist/style.css --minify",
43
+ "docs:build": "vite build --mode docs",
38
44
  "typecheck": "vue-tsc --noEmit",
39
45
  "lint": "eslint . --ext .vue,.ts",
40
46
  "prepublishOnly": "npm run build"
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { watch } from 'vue'
3
+
4
+ import { useBlueLoading } from '../composables/useBlueLoading'
5
+ import { useBlueOs } from '../composables/useBlueOs'
6
+ import { applyBlueTheme } from '../utils/theme'
7
+ import BlueCard from './BlueCard.vue'
8
+ import BlueLoadingDialog from './BlueLoadingDialog.vue'
9
+ import BlueSnackbar from './BlueSnackbar.vue'
10
+
11
+ /**
12
+ * The whole of an extension's page: BlueOS's backdrop, one card in the middle of it, and the two
13
+ * pieces of feedback every extension needs mounted once. Wrap your panels in it and the notices
14
+ * raised through useBlueSnackbar and the waits raised through useBlueLoading appear by themselves.
15
+ */
16
+ const props = defineProps<{
17
+ title: string
18
+ /** An image beside the title, such as the extension's own mark. */
19
+ logo?: string
20
+ /** An mdi name beside the title, for an extension with no mark of its own. */
21
+ icon?: string
22
+ /** How wide the card is allowed to get (default '615px'). */
23
+ width?: string
24
+ /**
25
+ * What is painted behind the card (default BlueOS's own blue). An extension is served in an
26
+ * iframe whose canvas is opaque, so leaving the page transparent shows black rather than the
27
+ * page behind it.
28
+ */
29
+ backdrop?: string
30
+ /** Keep the kit's own primary instead of the vehicle's. */
31
+ ignoreHostTheme?: boolean
32
+ }>()
33
+
34
+ const { loading, hideLoading } = useBlueLoading()
35
+ const { primaryColor } = useBlueOs()
36
+
37
+ watch(
38
+ primaryColor,
39
+ (color) => {
40
+ if (!props.ignoreHostTheme && color) applyBlueTheme({ primary: color })
41
+ },
42
+ { immediate: true }
43
+ )
44
+ </script>
45
+
46
+ <template>
47
+ <div
48
+ class="min-h-screen w-full px-4 py-6"
49
+ :style="{ backgroundColor: backdrop || '#0C577B' }"
50
+ >
51
+ <BlueCard
52
+ :title="title"
53
+ :logo="logo"
54
+ :icon="icon"
55
+ :width="width"
56
+ >
57
+ <template
58
+ v-if="$slots.actions"
59
+ #actions
60
+ >
61
+ <slot name="actions" />
62
+ </template>
63
+
64
+ <slot />
65
+
66
+ <template
67
+ v-if="$slots.footer"
68
+ #footer
69
+ >
70
+ <slot name="footer" />
71
+ </template>
72
+ </BlueCard>
73
+
74
+ <BlueSnackbar />
75
+
76
+ <!-- Dismissing is the only thing the overlay reports, and only a dismissible one can. -->
77
+ <BlueLoadingDialog
78
+ :model-value="loading.show"
79
+ :message="loading.message"
80
+ :dismissible="loading.dismissible"
81
+ @update:model-value="hideLoading"
82
+ />
83
+ </div>
84
+ </template>
@@ -0,0 +1,196 @@
1
+ <script setup lang="ts">
2
+ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
+
4
+ import type { BannerContent, BannerSeverity } from '../types/banner'
5
+
6
+ interface Look {
7
+ icon: string
8
+ color: string
9
+ background: string
10
+ border: string
11
+ }
12
+
13
+ /**
14
+ * A note that keeps to a square until it is asked for: shut, it is its icon; open, it is one line
15
+ * of message, and only its width moves between the two, so nothing around it is pushed about.
16
+ */
17
+ const LOOKS: Record<BannerSeverity, Look> = {
18
+ error: { icon: 'mdi-alert-circle-outline', color: '#EF9A9A', background: '#CF667926', border: '#CF667955' },
19
+ warning: { icon: 'mdi-alert', color: '#FFB74D', background: '#FB8C0022', border: '#FB8C0055' },
20
+ info: { icon: 'mdi-information-outline', color: '#81D4FA', background: '#4FC3F71A', border: '#4FC3F744' },
21
+ success: { icon: 'mdi-check-circle-outline', color: '#A5D6A7', background: '#66BB6A22', border: '#66BB6A55' },
22
+ }
23
+
24
+ const props = withDefaults(
25
+ defineProps<
26
+ BannerContent & {
27
+ /** Whether it starts open (default true). */
28
+ expanded?: boolean
29
+ }
30
+ >(),
31
+ // An absent boolean prop is `false` unless a default says otherwise, which would start every
32
+ // banner shut.
33
+ {
34
+ severity: undefined,
35
+ icon: undefined,
36
+ color: undefined,
37
+ background: undefined,
38
+ border: undefined,
39
+ expanded: undefined,
40
+ }
41
+ )
42
+
43
+ const emit = defineEmits<{
44
+ (e: 'update:expanded', value: boolean): void
45
+ }>()
46
+
47
+ const box = ref<HTMLElement | null>(null)
48
+ const reveal = ref<HTMLElement | null>(null)
49
+ const label = ref<HTMLElement | null>(null)
50
+
51
+ const isOpen = ref(props.expanded ?? true)
52
+ const messageWidth = ref(0)
53
+ const measured = ref(false)
54
+
55
+ const look = computed(() => LOOKS[props.severity ?? 'info'])
56
+
57
+ const boxStyle = computed(() => ({
58
+ color: props.color ?? look.value.color,
59
+ backgroundColor: props.background ?? look.value.background,
60
+ borderColor: props.border ?? look.value.border,
61
+ }))
62
+
63
+ // Shut has nothing to show and needs no measurement; open before the first one takes whatever room
64
+ // there is, which is what the measurement will say anyway.
65
+ const revealStyle = computed(() => {
66
+ if (!isOpen.value) {
67
+ return { width: '0px' }
68
+ }
69
+ return measured.value ? { width: `${messageWidth.value}px` } : undefined
70
+ })
71
+
72
+ // Held at its measured width in either state, so opening uncovers a line laid out once rather than
73
+ // one relaid on every frame of the way.
74
+ const labelStyle = computed(() => (measured.value ? { width: `${messageWidth.value}px` } : undefined))
75
+
76
+ // The room there is for the message and the width it asks for, read off elements laid out by their
77
+ // content and put back within the one task, so nothing is painted in between.
78
+ function measure(): void {
79
+ const button = box.value
80
+ const pane = reveal.value
81
+ const message = label.value
82
+ if (!button || !pane || !message) {
83
+ return
84
+ }
85
+ const buttonStyle = button.style.cssText
86
+ const paneStyle = pane.style.cssText
87
+ const messageStyle = message.style.cssText
88
+ const putBack = (): void => {
89
+ button.style.cssText = buttonStyle
90
+ pane.style.cssText = paneStyle
91
+ message.style.cssText = messageStyle
92
+ // Resolve the restored width while transitions are off: `auto` is not a width an animation can
93
+ // start from, and the banner would jump to its new size rather than move to it.
94
+ void pane.getBoundingClientRect()
95
+ button.classList.remove('bluevue-banner--measuring')
96
+ }
97
+ button.classList.add('bluevue-banner--measuring')
98
+
99
+ button.style.width = '100%'
100
+ pane.style.flex = '1 1 auto'
101
+ pane.style.width = 'auto'
102
+ message.style.width = '0px'
103
+ const room = pane.getBoundingClientRect().width
104
+ // Nothing to measure against inside something closed or off screen, and the last measurement
105
+ // beats none.
106
+ if (room <= 0) {
107
+ putBack()
108
+ return
109
+ }
110
+
111
+ message.style.width = 'max-content'
112
+ const wanted = Math.ceil(message.getBoundingClientRect().width)
113
+
114
+ // A message too long for the room keeps its line and loses its tail to the edge, the pointer
115
+ // being where the whole of it is read either way.
116
+ messageWidth.value = Math.min(wanted, Math.floor(room))
117
+
118
+ putBack()
119
+ measured.value = true
120
+ }
121
+
122
+ function toggle(): void {
123
+ // A click is proof of being on screen, which a banner mounted inside a closed panel was not.
124
+ measure()
125
+ isOpen.value = !isOpen.value
126
+ emit('update:expanded', isOpen.value)
127
+ }
128
+
129
+ let observer: ResizeObserver | null = null
130
+
131
+ onMounted(() => {
132
+ measure()
133
+ // The message is text, so the width it asks for changes when the font arrives.
134
+ void document.fonts?.ready.then(measure)
135
+ const parent = box.value?.parentElement
136
+ if (parent) {
137
+ // Width changes only, and on the next frame: laying the message out again from inside the
138
+ // notification resizes what is being reported on, which the browser calls a loop.
139
+ let room = parent.clientWidth
140
+ observer = new ResizeObserver(() => {
141
+ if (parent.clientWidth !== room) {
142
+ room = parent.clientWidth
143
+ requestAnimationFrame(measure)
144
+ }
145
+ })
146
+ observer.observe(parent)
147
+ }
148
+ })
149
+
150
+ onBeforeUnmount(() => {
151
+ observer?.disconnect()
152
+ observer = null
153
+ })
154
+
155
+ // After the render, since a new message is measured on screen rather than in the props.
156
+ watch(() => props.text, measure, { flush: 'post' })
157
+
158
+ watch(
159
+ () => props.expanded,
160
+ (value) => {
161
+ if (value !== undefined) {
162
+ isOpen.value = value
163
+ }
164
+ }
165
+ )
166
+ </script>
167
+
168
+ <template>
169
+ <button
170
+ ref="box"
171
+ type="button"
172
+ class="bluevue-banner bluevue-elevation-1-soft flex w-fit items-center rounded-[6px] border text-left cursor-pointer"
173
+ :style="boxStyle"
174
+ :aria-expanded="isOpen"
175
+ :aria-label="text"
176
+ :title="text"
177
+ @click="toggle"
178
+ >
179
+ <span
180
+ class="mdi bluevue-banner__icon shrink-0"
181
+ :class="props.icon ?? look.icon"
182
+ />
183
+ <span
184
+ ref="reveal"
185
+ class="bluevue-banner__reveal overflow-hidden"
186
+ :class="measured ? 'shrink-0' : ''"
187
+ :style="revealStyle"
188
+ >
189
+ <span
190
+ ref="label"
191
+ class="bluevue-banner__text block whitespace-nowrap"
192
+ :style="labelStyle"
193
+ >{{ text }}</span>
194
+ </span>
195
+ </button>
196
+ </template>
@@ -0,0 +1,47 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+
4
+ import type { BannerContent } from '../types/banner'
5
+ import BlueBanner from './BlueBanner.vue'
6
+
7
+ /**
8
+ * A row of banners that opens one at a time: the opened one takes the room the others leave, and
9
+ * the ones already read gather at the end of the row.
10
+ */
11
+ const props = defineProps<{
12
+ /** The banners, in the order they sit in until one of them is opened. Messages identify them. */
13
+ banners: BannerContent[]
14
+ }>()
15
+
16
+ const opened = ref<string | null>(null)
17
+ const promoted = ref<string[]>([])
18
+
19
+ // The row is the banners still to be read followed by the ones that have been, in the order they
20
+ // were opened. Ordering the row rather than reordering it leaves the elements where they are: a
21
+ // banner moved in the DOM loses both the transition it is in the middle of and the focus it holds.
22
+ function orderOf(banner: BannerContent): number {
23
+ const moved = promoted.value.indexOf(banner.text)
24
+ return moved === -1 ? props.banners.indexOf(banner) : props.banners.length + moved
25
+ }
26
+
27
+ function onToggle(text: string, open: boolean): void {
28
+ opened.value = open ? text : null
29
+ if (open) {
30
+ promoted.value = [...promoted.value.filter((moved) => moved !== text), text]
31
+ }
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <div class="flex w-full min-w-0 items-center gap-2">
37
+ <BlueBanner
38
+ v-for="banner in props.banners"
39
+ :key="banner.text"
40
+ v-bind="banner"
41
+ :expanded="opened === banner.text"
42
+ :class="opened === banner.text ? 'min-w-0 flex-1 !w-auto' : 'shrink-0'"
43
+ :style="{ order: orderOf(banner) }"
44
+ @update:expanded="onToggle(banner.text, $event)"
45
+ />
46
+ </div>
47
+ </template>
@@ -0,0 +1,134 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ import BlueIcon from './BlueIcon.vue'
5
+ import BlueSpinner from './BlueSpinner.vue'
6
+
7
+ /**
8
+ * The standard action. A button with no colour of its own is a secondary action and reads as a
9
+ * barely-there fill; the filled one is what the panel is actually asking to be done, and a panel
10
+ * asks for one thing at a time.
11
+ */
12
+ const props = defineProps<{
13
+ /**
14
+ * 'tonal' (default) for a secondary action, 'filled' for the one the panel is asking for,
15
+ * 'text' for a quiet one, 'icon' for a glyph on its own.
16
+ */
17
+ variant?: 'tonal' | 'filled' | 'text' | 'icon'
18
+ /** The fill of a filled button, and the glyph colour of the others (default the primary token). */
19
+ color?: string
20
+ /** Height and inset (default 'regular', which is the height of a button group beside it). */
21
+ density?: 'compact' | 'regular' | 'comfortable'
22
+ /** An mdi name before the label, or the whole of an icon button. */
23
+ icon?: string
24
+ /** An mdi name after the label. */
25
+ appendIcon?: string
26
+ /** Swaps the icon for a spinner and takes the button out of reach until it is done. */
27
+ loading?: boolean
28
+ disabled?: boolean
29
+ /** Takes the whole width it is given, for a button ending a narrow panel. */
30
+ block?: boolean
31
+ /** Hover text, and the name assistive technology reads on an icon button. */
32
+ tooltip?: string
33
+ theme?: 'light' | 'dark'
34
+ type?: 'button' | 'submit' | 'reset'
35
+ }>()
36
+
37
+ defineEmits<{
38
+ (event: 'click', value: MouseEvent): void
39
+ }>()
40
+
41
+ const HEIGHT = { compact: 24, regular: 30, comfortable: 36 } as const
42
+ const PADDING = { compact: 'px-2', regular: 'px-3', comfortable: 'px-4' } as const
43
+
44
+ const variant = computed(() => props.variant ?? 'tonal')
45
+ const density = computed(() => props.density ?? 'regular')
46
+ const isDark = computed(() => props.theme !== 'light')
47
+ const height = computed(() => HEIGHT[density.value])
48
+ const glyphSize = computed(() => (density.value === 'compact' ? 14 : 16))
49
+
50
+ // A filled or tonal button reads as a chip, so out of reach it becomes a flat grey one. A text or
51
+ // icon button has no chip to grey, and fading it is the whole of the statement.
52
+ const look = computed(() => {
53
+ if (props.disabled) {
54
+ return variant.value === 'text' || variant.value === 'icon'
55
+ ? 'opacity-30 cursor-not-allowed'
56
+ : 'opacity-30 cursor-not-allowed bg-[#9e9e9e80] text-[#afafaf]'
57
+ }
58
+ switch (variant.value) {
59
+ case 'filled':
60
+ return 'bluevue-elevation-1 cursor-pointer text-white hover:brightness-125'
61
+ case 'text':
62
+ return isDark.value
63
+ ? 'cursor-pointer text-[#ffffffcc] hover:bg-[#ffffff11] hover:text-white'
64
+ : 'cursor-pointer text-[#000000cc] hover:bg-[#00000011] hover:text-black'
65
+ case 'icon':
66
+ return isDark.value
67
+ ? 'cursor-pointer text-[#ffffffaa] hover:text-white'
68
+ : 'cursor-pointer text-[#00000099] hover:text-black'
69
+ default:
70
+ return isDark.value
71
+ ? 'cursor-pointer bg-[#ffffff11] text-white hover:bg-[#ffffff22]'
72
+ : 'cursor-pointer bg-[#00000011] text-black hover:bg-[#00000022]'
73
+ }
74
+ })
75
+
76
+ const fill = computed(() => (variant.value === 'filled' && !props.disabled ? props.color || 'var(--bluevue-primary)' : undefined))
77
+
78
+ // The glyph of a text or icon button follows the colour it was given; a filled one draws its
79
+ // contents in white over the fill, and a tonal one in the theme's own text colour.
80
+ const glyphColor = computed(() =>
81
+ props.color && (variant.value === 'text' || variant.value === 'icon') ? props.color : undefined
82
+ )
83
+ </script>
84
+
85
+ <template>
86
+ <button
87
+ :type="type || 'button'"
88
+ :disabled="disabled"
89
+ :title="tooltip"
90
+ :aria-label="variant === 'icon' ? tooltip : undefined"
91
+ :aria-busy="loading || undefined"
92
+ class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap transition-colors duration-200"
93
+ :class="[
94
+ look,
95
+ variant === 'icon' ? 'rounded-[6px]' : 'rounded-[4px]',
96
+ variant === 'icon' ? '' : PADDING[density],
97
+ block ? 'w-full' : '',
98
+ // A button waiting on its own result stays where it is and reads as busy rather than
99
+ // inviting a second press.
100
+ loading ? 'pointer-events-none' : '',
101
+ ]"
102
+ :style="{
103
+ height: `${height}px`,
104
+ width: variant === 'icon' ? `${height}px` : undefined,
105
+ backgroundColor: fill,
106
+ }"
107
+ @click="$emit('click', $event)"
108
+ >
109
+ <BlueSpinner
110
+ v-if="loading"
111
+ :size="glyphSize"
112
+ :color="variant === 'filled' ? '#ffffff' : undefined"
113
+ />
114
+ <BlueIcon
115
+ v-else-if="icon"
116
+ :name="icon"
117
+ :size="glyphSize"
118
+ :color="glyphColor"
119
+ />
120
+ <span
121
+ v-if="variant !== 'icon'"
122
+ class="min-w-0 truncate font-medium uppercase tracking-wide"
123
+ :class="density === 'compact' ? 'text-[12px]' : 'text-[13px]'"
124
+ >
125
+ <slot />
126
+ </span>
127
+ <BlueIcon
128
+ v-if="appendIcon && variant !== 'icon'"
129
+ :name="appendIcon"
130
+ :size="glyphSize"
131
+ :color="glyphColor"
132
+ />
133
+ </button>
134
+ </template>