From 205e1045a6c033ff6826b1fee78ee01a982b15ec Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 16 Sep 2025 01:33:46 -0700 Subject: [PATCH] add select component with bits-ui --- src/assets/icons/close.svg | 3 + src/lib/components/ui/select/Select.svelte | 117 ++++++++ .../components/ui/select/select.module.scss | 253 ++++++++++++++++++ 3 files changed, 373 insertions(+) create mode 100644 src/assets/icons/close.svg create mode 100644 src/lib/components/ui/select/Select.svelte create mode 100644 src/lib/components/ui/select/select.module.scss diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg new file mode 100644 index 00000000..0b1bbd9f --- /dev/null +++ b/src/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/lib/components/ui/select/Select.svelte b/src/lib/components/ui/select/Select.svelte new file mode 100644 index 00000000..b33877d7 --- /dev/null +++ b/src/lib/components/ui/select/Select.svelte @@ -0,0 +1,117 @@ + + + + + {#if selected?.image} + {selected.label} + {/if} + {selected?.label || placeholder} + + + + + + +
+ +
+ + + {#each options as option} + + {#if option.image} + {option.label} + {/if} + {option.label} + + + + + {/each} + + +
+ +
+
+
\ No newline at end of file diff --git a/src/lib/components/ui/select/select.module.scss b/src/lib/components/ui/select/select.module.scss new file mode 100644 index 00000000..90392e0e --- /dev/null +++ b/src/lib/components/ui/select/select.module.scss @@ -0,0 +1,253 @@ +@use 'themes/spacing'; +@use 'themes/colors'; +@use 'themes/typography'; +@use 'themes/layout'; +@use 'themes/effects'; +@use 'themes/mixins'; + +.trigger { + align-items: center; + background-color: var(--input-bg); + border-radius: layout.$input-corner; + border: 2px solid transparent; + display: flex; + gap: spacing.$unit; + padding: (spacing.$unit * 1.5) spacing.$unit-2x; + white-space: nowrap; + cursor: pointer; + transition: background-color 0.18s ease-out; + + &.small { + & > span:not(.icon) { + font-size: typography.$font-small; + margin: 0; + max-width: 200px; + } + + @include mixins.breakpoint(tablet) { + &::before { + content: ''; + display: block; + width: calc(spacing.$unit-2x - 1px); + } + + & > span:not(.icon) { + width: 100%; + max-width: inherit; + text-align: center; + } + } + } + + &.grow { + flex-grow: 1; + } + + &.left { + flex-grow: 1; + width: 100%; + } + + &.right { + flex-grow: 0; + text-align: right; + min-width: 12rem; + } + + &.bound { + background-color: var(--select-contained-bg); + + &:hover { + background-color: var(--select-contained-bg-hover); + + &.disabled { + background-color: var(--select-contained-bg); + } + } + } + + &.full { + width: 100%; + } + + &.table { + min-width: spacing.$unit * 30; + + @include mixins.breakpoint(phone) { + width: 100%; + } + } + + &.hidden { + display: none; + } + + &:hover { + background-color: var(--input-bg-hover); + + span:not(.icon), + &[data-placeholder] > span:not(.icon) { + color: var(--text-primary); + } + + .icon svg { + fill: var(--text-primary); + } + } + + &.disabled:hover { + background-color: var(--input-bg); + cursor: not-allowed; + } + + &[data-placeholder='true'] > span:not(.icon) { + color: var(--text-secondary); + } + + & > span:not(.icon) { + color: var(--text-primary); + flex-grow: 1; + font-size: typography.$font-regular; + text-align: left; + } + + img { + width: spacing.$unit-4x; + height: auto; + } + + .icon { + display: flex; + align-items: center; + + svg { + fill: var(--icon-secondary); + } + } +} + +.select { + animation: scaleIn effects.$duration-zoom ease-out; + background: var(--dialog-bg); + border-radius: layout.$card-corner; + border: 1px solid rgba(0, 0, 0, 0.24); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16); + padding: 0 spacing.$unit; + min-width: var(--radix-select-trigger-width); + transform-origin: var(--radix-select-content-transform-origin); + max-height: 40vh; + z-index: 40; + + &.bound { + background-color: var(--select-content-contained-bg); + } + + .scroll.up, + .scroll.down { + padding: spacing.$unit 0; + text-align: center; + + &:hover svg { + fill: var(--icon-secondary-hover); + } + + svg { + fill: var(--icon-secondary); + } + } + + .scroll.up { + transform: scale(1, -1); + } + + @keyframes scaleIn { + 0% { + opacity: 0; + transform: scale(0); + } + 20% { + opacity: 0.2; + transform: scale(0.4); + } + 40% { + opacity: 0.4; + transform: scale(0.8); + } + 60% { + opacity: 0.6; + transform: scale(1); + } + 65% { + opacity: 0.65; + transform: scale(1.1); + } + 70% { + opacity: 0.7; + transform: scale(1); + } + 75% { + opacity: 0.75; + transform: scale(0.98); + } + 80% { + opacity: 0.8; + transform: scale(1.02); + } + 90% { + opacity: 0.9; + transform: scale(0.96); + } + 100% { + opacity: 1; + transform: scale(1); + } + } +} + +.item { + align-items: center; + border-radius: layout.$item-corner-small; + color: var(--text-primary); + cursor: pointer; + display: flex; + gap: spacing.$unit; + padding: spacing.$unit spacing.$unit-2x; + position: relative; + user-select: none; + transition: background-color 0.12s ease-out; + + &:hover { + background-color: var(--option-bg-hover); + } + + &[data-disabled] { + color: var(--text-tertiary); + cursor: not-allowed; + opacity: 0.5; + } + + &[data-selected] { + background-color: var(--option-bg-hover); + font-weight: typography.$medium; + } + + img { + width: spacing.$unit-3x; + height: auto; + } + + span { + flex-grow: 1; + } + + .indicator { + display: flex; + align-items: center; + justify-content: center; + margin-left: auto; + + svg { + fill: var(--accent-blue); + } + } +} \ No newline at end of file