diff --git a/apps/demo/src/App.scss b/apps/demo/src/App.scss index e92e76c..e9abb4f 100644 --- a/apps/demo/src/App.scss +++ b/apps/demo/src/App.scss @@ -33,6 +33,13 @@ flex-wrap: wrap; } + &__button-grid { + display: grid; + grid-template-columns: repeat(4, minmax(140px, max-content)); + gap: 1rem; + align-items: center; + } + &__switches, &__checkboxes { display: flex; @@ -87,4 +94,43 @@ margin: 0; } } + + &__select-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 2rem; + } + + &__select-column { + display: flex; + flex-direction: column; + gap: 1rem; + } + + &__select-item { + max-width: 220px; + } +} + +.is-demo-hover.mdk-button--variant-primary { + background: linear-gradient( + 0deg, + var(--mdk-button-primary-bg-hover-overlay) 0%, + var(--mdk-button-primary-bg-hover-overlay) 100% + ), + var(--mdk-button-primary-bg); + color: var(--mdk-button-primary-text-hover); +} + +.is-demo-hover.mdk-button--variant-secondary { + border-color: var(--mdk-button-secondary-border-hover); +} + +.is-demo-hover.mdk-button--variant-danger { + opacity: 0.9; +} + +.is-demo-hover.mdk-button--variant-outline { + background: hsl(var(--accent)); + color: hsl(var(--accent-foreground)); } diff --git a/apps/demo/src/App.tsx b/apps/demo/src/App.tsx index a94faa2..ebf4f07 100644 --- a/apps/demo/src/App.tsx +++ b/apps/demo/src/App.tsx @@ -24,13 +24,36 @@ import { DialogHeader, DialogTitle, DialogTrigger, + Dropdown, Label, + Select, Switch, } from '@mining-sdk/core' import './App.scss' function App(): JSX.Element { + const selectOptions = [ + { value: 'item-1', label: 'Item 1' }, + { value: 'item-2', label: 'Item 2' }, + { value: 'item-3', label: 'Item 3' }, + { value: 'item-4', label: 'Item 4' }, + ] + + const dropdownItems = [ + { + key: 'group-1', + type: 'group' as const, + label: 'Items', + children: [ + { key: 'item-1', label: 'Item 1' }, + { key: 'item-2', label: 'Item 2' }, + { key: 'item-3', label: 'Item 3' }, + { key: 'item-4', label: 'Item 4' }, + ], + }, + ] + return (

@mining-sdk/core Component Demo

@@ -39,18 +62,87 @@ function App(): JSX.Element { {/* Buttons */}

Buttons

-
- - - +
+ - - + + + + + + + + + + + +
-
- - - +
+ + {/* Select & Dropdown */} +
+

Select & Dropdown

+
+
+

States

+
+ +
+
+ +
+
+ setOpen(true)} + disabled={disabled} + /> +
+ {showClear && ( + + )} +
+ + + +
+ {filteredOptions.length ? ( + filteredOptions.map((option) => { + const selected = isValueSelected(currentValue, option.value) + return ( + + ) + }) + ) : ( +
No options
+ )} +
+
+
+ +
+ ) + }, +) +TagsSelect.displayName = 'TagsSelect' + +const Select = React.forwardRef( + ( + { + options: optionsProp, + placeholder, + value, + defaultValue, + onChange, + onSelect, + onClear, + allowClear, + mode, + tokenSeparators = [','], + status = '', + size, + loading, + disabled, + className, + dropdownClassName, + suffixIcon, + children, + }, + ref, + ) => { + const resolvedSize = sizeToSize(size) + const options = React.useMemo( + () => normalizeOptions(optionsProp, children), + [optionsProp, children], + ) + const isTagMode = mode === 'tags' || mode === 'multiple' + + if (isTagMode) { + return ( + onChange?.(next)} + onSelect={onSelect} + onClear={onClear} + allowClear={allowClear} + tokenSeparators={tokenSeparators} + status={status} + size={resolvedSize} + loading={loading} + disabled={disabled} + className={className} + dropdownClassName={dropdownClassName} + suffixIcon={suffixIcon} + allowCustomValues={mode === 'tags'} + /> + ) + } + + return ( + + ) + }, +) +Select.displayName = 'Select' + +function ChevronIcon(): React.ReactElement { + return ( + + + + ) +} + +function CheckIcon(): React.ReactElement { + return ( + + + + ) +} + +const SelectWithOption = Object.assign(Select, { Option: SelectOption }) + +export { SelectWithOption as Select, SelectOption } diff --git a/packages/core/src/components/select/styles.scss b/packages/core/src/components/select/styles.scss new file mode 100644 index 0000000..808ed82 --- /dev/null +++ b/packages/core/src/components/select/styles.scss @@ -0,0 +1,235 @@ +/** + * Select Component Styles + */ + +.mdk-select { + display: inline-flex; + flex-direction: column; + width: 100%; + font-size: var(--mdk-font-size); + color: var(--mdk-input-text); +} + +.mdk-select__control { + position: relative; + display: flex; + align-items: center; + min-height: 2.5rem; + border: 1px solid var(--mdk-input-border); + background: var(--mdk-input-bg); + border-radius: var(--mdk-input-radius); + padding: 0 2.5rem 0 0.75rem; + gap: 0.5rem; + transition: border-color 0.2s, box-shadow 0.2s; + + &:hover { + border-color: var(--mdk-input-border-hover); + background: var(--mdk-input-bg-hover); + } + + &:focus-within { + border-color: var(--mdk-input-border-active); + box-shadow: var(--mdk-input-focus-shadow); + } +} + +.mdk-select__trigger { + all: unset; + display: flex; + align-items: center; + width: 100%; + cursor: pointer; +} + +.mdk-select__value { + flex: 1; + + &[data-placeholder] { + color: var(--mdk-input-placeholder); + } +} + +.mdk-select__suffix { + position: absolute; + right: 0.75rem; + display: inline-flex; + align-items: center; + color: var(--mdk-input-placeholder); +} + +.mdk-select__clear { + position: absolute; + right: 2.25rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; + border: none; + background: transparent; + color: var(--mdk-input-placeholder); + cursor: pointer; +} + +.mdk-select__chevron { + width: 1rem; + height: 1rem; +} + +.mdk-select__spinner { + width: 1rem; + height: 1rem; + border-radius: 9999px; + border: 2px solid rgba(255, 255, 255, 0.2); + border-top-color: var(--mdk-input-text); + animation: mdk-select-spin 0.7s linear infinite; +} + +.mdk-select__content { + min-width: 12rem; + background: var(--mdk-dropdown-bg); + color: var(--mdk-dropdown-item-text); + border: 1px solid var(--mdk-dropdown-border); + border-radius: var(--mdk-input-radius); + box-shadow: var(--mdk-dropdown-shadow); + padding: 0.25rem; + z-index: 50; + max-height: 14rem; + overflow-y: auto; +} + +.mdk-select__viewport { + display: flex; + flex-direction: column; + gap: 0.125rem; +} + +.mdk-select__item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.75rem; + border-radius: calc(var(--mdk-input-radius) + 2px); + cursor: pointer; + font-size: 0.875rem; + color: var(--mdk-dropdown-item-text); + + &:not(:last-child) { + border-bottom: 1px solid var(--mdk-dropdown-divider); + } + + &[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + } + + &[data-highlighted] { + background: var(--mdk-dropdown-item-hover-bg); + color: var(--mdk-dropdown-item-hover-text); + outline: none; + } + + &[data-state='checked'] { + background: var(--mdk-dropdown-item-selected-bg); + color: var(--mdk-dropdown-item-selected-text); + } +} + +.mdk-select__item--selected { + background: var(--mdk-dropdown-item-selected-bg); + color: var(--mdk-dropdown-item-selected-text); +} + +.mdk-select__item-indicator { + display: inline-flex; + align-items: center; + color: currentColor; +} + +.mdk-select__empty { + padding: 0.5rem 0.75rem; + color: var(--mdk-input-placeholder); + font-size: 0.875rem; +} + +.mdk-select--tags .mdk-select__control { + flex-wrap: wrap; + padding: 0.25rem 2.5rem 0.25rem 0.5rem; +} + +.mdk-select__tags { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem; + flex: 1; +} + +.mdk-select__tag { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.125rem 0.5rem; + background: rgba(255, 255, 255, 0.08); + color: var(--mdk-input-text); + border-radius: 9999px; + font-size: 0.75rem; +} + +.mdk-select__tag-remove { + border: none; + background: transparent; + color: inherit; + cursor: pointer; + font-size: 0.75rem; +} + +.mdk-select__input { + flex: 1; + min-width: 6rem; + border: none; + background: transparent; + color: var(--mdk-input-text); + font-size: 0.875rem; + outline: none; + padding: 0.25rem; +} + +.mdk-select__input::placeholder { + color: var(--mdk-input-placeholder); +} + +.mdk-select--size-sm .mdk-select__control { + min-height: 2rem; + font-size: 0.8125rem; +} + +.mdk-select--size-lg .mdk-select__control { + min-height: 3rem; + font-size: 1rem; +} + +.mdk-select--status-error .mdk-select__control { + border-color: var(--mdk-input-border-error); + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2); +} + +.mdk-select--disabled { + opacity: 0.6; + + .mdk-select__control { + cursor: not-allowed; + background: #1f1f1f; + border-color: #2a2a2a; + } + + .mdk-select__trigger { + cursor: not-allowed; + } +} + +@keyframes mdk-select-spin { + to { + transform: rotate(360deg); + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ca418c6..ada0b53 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -15,12 +15,13 @@ export * from './components/checkbox' export * from './components/dialog' // Re-export Radix primitives with namespaces to avoid conflicts export * as DropdownMenu from './components/dropdown-menu' +export { Dropdown } from './components/dropdown-menu' export * from './components/label' export * as Popover from './components/popover' export * as Progress from './components/progress' export * as RadioGroup from './components/radio-group' -export * as Select from './components/select' +export * from './components/select' export * as Separator from './components/separator' export * as Slider from './components/slider' export * from './components/switch' diff --git a/packages/core/src/styles.scss b/packages/core/src/styles.scss index 94ced37..1f65bfc 100644 --- a/packages/core/src/styles.scss +++ b/packages/core/src/styles.scss @@ -11,7 +11,9 @@ @use './components/button/styles' as button; @use './components/checkbox/styles' as checkbox; @use './components/dialog/styles' as dialog; +@use './components/dropdown-menu/styles' as dropdownMenu; @use './components/label/styles' as label; +@use './components/select/styles' as select; @use './components/switch/styles' as switch; // CSS Variables @@ -56,6 +58,50 @@ --mdk-switch-unchecked-bg: var(--mdk-color-background-light); --mdk-switch-thumb-shadow: rgba(0, 0, 0, 0.2); --mdk-switch-thumb-shadow-checked: rgba(0, 0, 0, 0.3); + /* MDK button tokens (moria baseline) */ + --mdk-radius: 0px; + --mdk-font-size: 14px; + --mdk-line-height: 22px; + --mdk-font-weight: 600; + --mdk-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.04); + + --mdk-button-primary-bg: #f7931a; + --mdk-button-primary-bg-hover: #c4730f; + --mdk-button-primary-text: #000000; + --mdk-button-primary-text-hover: #ffffff; + --mdk-button-primary-bg-hover-overlay: rgba(255, 255, 255, 0.15); + + --mdk-button-secondary-text: #ffffffcc; + --mdk-button-secondary-border: #ffffff33; + --mdk-button-secondary-border-hover: #f7931a80; + --mdk-button-secondary-bg: #17130f; + + --mdk-button-danger-bg: #ef4444; + --mdk-button-danger-text: #ffffff; + --mdk-button-danger-border: #ef4444; + + /* Input / Select tokens (moria baseline) */ + --mdk-input-bg: #2f2f2f; + --mdk-input-bg-hover: #3a3a3a; + --mdk-input-text: #d7d7d7; + --mdk-input-placeholder: #8b8b8b; + --mdk-input-border: #3a3a3a; + --mdk-input-border-hover: #5a5a5a; + --mdk-input-border-active: #f7931a; + --mdk-input-border-error: #ef4444; + --mdk-input-radius: 2px; + --mdk-input-focus-shadow: 0 0 0 2px rgba(247, 147, 26, 0.25); + + /* Dropdown / menu tokens */ + --mdk-dropdown-bg: #1b140f; + --mdk-dropdown-border: #3a2d1d; + --mdk-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); + --mdk-dropdown-item-text: #c9c9c9; + --mdk-dropdown-item-hover-bg: #2d2116; + --mdk-dropdown-item-hover-text: #ffffff; + --mdk-dropdown-item-selected-bg: #3a2a18; + --mdk-dropdown-item-selected-text: #f7931a; + --mdk-dropdown-divider: #2b2118; } .dark {