From 0d30f1f84459f5e81d6c87279e053177ae194f47 Mon Sep 17 00:00:00 2001 From: Roberto Di Lillo Date: Thu, 5 Feb 2026 10:16:37 +0100 Subject: [PATCH 1/5] feat: align mdk button styles with moria Update core button API and styles to match moria specs, and refresh demo layout to show normal, disabled, and hover states in a 4x3 grid. --- apps/demo/src/App.scss | 30 +++++ apps/demo/src/App.tsx | 45 +++++-- packages/core/src/components/button/index.tsx | 119 ++++++++++++++++-- .../core/src/components/button/styles.scss | 105 ++++++++++++---- packages/core/src/styles.scss | 22 ++++ 5 files changed, 275 insertions(+), 46 deletions(-) diff --git a/apps/demo/src/App.scss b/apps/demo/src/App.scss index 444293c..232c498 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; + } + &__button-sizes { display: flex; gap: 1rem; @@ -68,3 +75,26 @@ } } } + +.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 99d4042..0772a72 100644 --- a/apps/demo/src/App.tsx +++ b/apps/demo/src/App.tsx @@ -45,18 +45,37 @@ function App(): JSX.Element { {/* Buttons */}

Buttons

-
- - - +
+ - - -
-
- - - + + + + + + + + + + + +
@@ -79,7 +98,7 @@ function App(): JSX.Element { - + @@ -90,7 +109,7 @@ function App(): JSX.Element {

Alert Dialog

- + diff --git a/packages/core/src/components/button/index.tsx b/packages/core/src/components/button/index.tsx index 74eab75..580f6d3 100644 --- a/packages/core/src/components/button/index.tsx +++ b/packages/core/src/components/button/index.tsx @@ -2,29 +2,132 @@ import * as React from 'react' import { cn } from '../../utils' -export type ButtonVariant = 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' -export type ButtonSize = 'default' | 'sm' | 'lg' | 'icon' +export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'outline' | 'ghost' | 'link' +export type ButtonSize = 'sm' | 'md' | 'lg' | 'icon' +export type ButtonType = 'primary' | 'default' | 'dashed' | 'text' | 'link' +export type ButtonIconPosition = 'left' | 'right' +export type ButtonAntdSize = 'small' | 'middle' | 'large' +export type ButtonColor = 'primary' | 'secondary' | 'danger' | 'ghost' | 'link' | 'outline' export type ButtonProps = { variant?: ButtonVariant - size?: ButtonSize -} & React.ButtonHTMLAttributes + size?: ButtonSize | ButtonAntdSize + type?: ButtonType + htmlType?: React.ButtonHTMLAttributes['type'] + danger?: boolean + loading?: boolean + icon?: React.ReactNode + iconPosition?: ButtonIconPosition + fullWidth?: boolean + block?: boolean + color?: ButtonColor +} & Omit, 'type'> + +function typeToVariant(type?: ButtonType): ButtonVariant | undefined { + switch (type) { + case 'primary': + return 'primary' + case 'link': + return 'link' + case 'text': + return 'ghost' + case 'dashed': + return 'outline' + case 'default': + default: + return 'secondary' + } +} + +function sizeToSize(size?: string): ButtonSize { + switch (size) { + case 'sm': + case 'small': + return 'sm' + case 'lg': + case 'large': + return 'lg' + case 'icon': + return 'icon' + case 'md': + case 'middle': + default: + return 'md' + } +} /** * Button component with multiple variants and sizes * * @example * ```tsx - * + * * * * ``` */ const Button = React.forwardRef( - ({ className, variant = 'default', size = 'default', ...props }, ref) => { - const classes = cn('mdk-button', `mdk-button--${variant}`, `mdk-button--${size}`, className) + ( + { + className, + variant, + size = 'md', + type, + htmlType, + danger, + loading, + icon, + iconPosition = 'left', + fullWidth, + block, + color, + disabled, + children, + ...props + }, + ref, + ) => { + const resolvedVariant = + variant ?? (danger ? 'danger' : (color ?? typeToVariant(type) ?? 'secondary')) + const resolvedSize = sizeToSize(size) + const resolvedHtmlType = htmlType ?? 'button' + const isIconOnly = Boolean(icon) && !children + + const classes = cn( + 'mdk-button', + `mdk-button--variant-${resolvedVariant}`, + `mdk-button--size-${resolvedSize}`, + { + 'mdk-button--full-width': fullWidth || block, + 'mdk-button--loading': loading, + 'mdk-button--icon-only': isIconOnly, + }, + className, + ) - return + ) }, ) Button.displayName = 'Button' diff --git a/packages/core/src/components/button/styles.scss b/packages/core/src/components/button/styles.scss index 1c45cec..5e13e93 100644 --- a/packages/core/src/components/button/styles.scss +++ b/packages/core/src/components/button/styles.scss @@ -6,15 +6,21 @@ display: inline-flex; align-items: center; justify-content: center; - border-radius: var(--radius); - font-size: 0.875rem; - font-weight: 500; + border-radius: var(--mdk-button-radius); + font-size: var(--mdk-button-font-size); + line-height: var(--mdk-button-line-height); + font-weight: var(--mdk-button-font-weight); + gap: 0.5rem; + border: 1px solid transparent; + cursor: pointer; transition: colors 0.2s; + box-shadow: var(--mdk-button-shadow); &:focus-visible { outline: none; - ring: 2px solid hsl(var(--ring)); - ring-offset: 2px; + box-shadow: 0 0 0 2px hsl(var(--ring)); + outline: 2px solid transparent; + outline-offset: 2px; } &:disabled { @@ -23,28 +29,37 @@ } // Variants - &--default { - background: hsl(var(--primary)); - color: hsl(var(--primary-foreground)); + &--variant-primary { + background: var(--mdk-button-primary-bg); + color: var(--mdk-button-primary-text); &:hover { - background: hsl(var(--primary) / 0.9); + 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); } } - &--destructive { - background: hsl(var(--destructive)); - color: hsl(var(--destructive-foreground)); + &--variant-danger { + background: var(--mdk-button-danger-bg); + color: var(--mdk-button-danger-text); + border-color: var(--mdk-button-danger-border); + box-shadow: none; &:hover { - background: hsl(var(--destructive) / 0.9); + opacity: 0.9; } } - &--outline { + &--variant-outline { border: 1px solid hsl(var(--input)); background: hsl(var(--background)); color: hsl(var(--foreground)); + box-shadow: none; &:hover { background: hsl(var(--accent)); @@ -52,18 +67,21 @@ } } - &--secondary { - background: hsl(var(--secondary)); - color: hsl(var(--secondary-foreground)); + &--variant-secondary { + background: var(--mdk-button-secondary-bg); + color: var(--mdk-button-secondary-text); + border-color: var(--mdk-button-secondary-border); + box-shadow: none; &:hover { - background: hsl(var(--secondary) / 0.8); + border-color: var(--mdk-button-secondary-border-hover); } } - &--ghost { + &--variant-ghost { background: transparent; color: hsl(var(--foreground)); + box-shadow: none; &:hover { background: hsl(var(--accent)); @@ -71,11 +89,12 @@ } } - &--link { + &--variant-link { background: transparent; color: hsl(217 91% 60%); // Bright blue for visibility text-decoration: underline; text-underline-offset: 4px; + box-shadow: none; &:hover { text-decoration: underline; @@ -84,24 +103,60 @@ } // Sizes - &--sm { + &--size-sm { height: 2.25rem; padding: 0 0.75rem; border-radius: calc(var(--radius) - 2px); } - &--default { + &--size-md { height: 2.5rem; - padding: 0.5rem 1rem; + padding: 0.5625rem 3.5rem; + min-width: 8.75rem; } - &--lg { + &--size-lg { height: 2.75rem; padding: 0.5rem 2rem; } - &--icon { + &--size-icon { height: 2.5rem; width: 2.5rem; + padding: 0; + } +} + +.mdk-button--full-width { + width: 100%; +} + +.mdk-button--icon-only { + padding: 0; + aspect-ratio: 1 / 1; +} + +.mdk-button__spinner { + width: 1rem; + height: 1rem; + border-radius: 9999px; + border: 2px solid hsl(var(--foreground) / 0.3); + border-top-color: hsl(var(--foreground)); + animation: mdk-button-spin 0.7s linear infinite; +} + +.mdk-button__icon { + display: inline-flex; + align-items: center; +} + +.mdk-button__label { + display: inline-flex; + align-items: center; +} + +@keyframes mdk-button-spin { + to { + transform: rotate(360deg); } } diff --git a/packages/core/src/styles.scss b/packages/core/src/styles.scss index c483c7e..ff19b31 100644 --- a/packages/core/src/styles.scss +++ b/packages/core/src/styles.scss @@ -37,6 +37,28 @@ --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; --radius: 0.5rem; + + /* MDK button tokens (moria baseline) */ + --mdk-button-radius: 0px; + --mdk-button-font-size: 14px; + --mdk-button-line-height: 22px; + --mdk-button-font-weight: 600; + --mdk-button-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; } .dark { From e896e1a658f97bb809a3d5d4f055c36d6f58093b Mon Sep 17 00:00:00 2001 From: Roberto Di Lillo Date: Thu, 5 Feb 2026 10:34:39 +0100 Subject: [PATCH 2/5] fix: restore native button type prop --- packages/core/src/components/button/index.tsx | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/packages/core/src/components/button/index.tsx b/packages/core/src/components/button/index.tsx index 580f6d3..f2e8727 100644 --- a/packages/core/src/components/button/index.tsx +++ b/packages/core/src/components/button/index.tsx @@ -4,7 +4,6 @@ import { cn } from '../../utils' export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'outline' | 'ghost' | 'link' export type ButtonSize = 'sm' | 'md' | 'lg' | 'icon' -export type ButtonType = 'primary' | 'default' | 'dashed' | 'text' | 'link' export type ButtonIconPosition = 'left' | 'right' export type ButtonAntdSize = 'small' | 'middle' | 'large' export type ButtonColor = 'primary' | 'secondary' | 'danger' | 'ghost' | 'link' | 'outline' @@ -12,8 +11,6 @@ export type ButtonColor = 'primary' | 'secondary' | 'danger' | 'ghost' | 'link' export type ButtonProps = { variant?: ButtonVariant size?: ButtonSize | ButtonAntdSize - type?: ButtonType - htmlType?: React.ButtonHTMLAttributes['type'] danger?: boolean loading?: boolean icon?: React.ReactNode @@ -21,23 +18,7 @@ export type ButtonProps = { fullWidth?: boolean block?: boolean color?: ButtonColor -} & Omit, 'type'> - -function typeToVariant(type?: ButtonType): ButtonVariant | undefined { - switch (type) { - case 'primary': - return 'primary' - case 'link': - return 'link' - case 'text': - return 'ghost' - case 'dashed': - return 'outline' - case 'default': - default: - return 'secondary' - } -} +} & React.ButtonHTMLAttributes function sizeToSize(size?: string): ButtonSize { switch (size) { @@ -72,8 +53,6 @@ const Button = React.forwardRef( className, variant, size = 'md', - type, - htmlType, danger, loading, icon, @@ -82,15 +61,15 @@ const Button = React.forwardRef( block, color, disabled, + type: nativeType, children, ...props }, ref, ) => { - const resolvedVariant = - variant ?? (danger ? 'danger' : (color ?? typeToVariant(type) ?? 'secondary')) + const resolvedVariant = variant ?? (danger ? 'danger' : (color ?? 'secondary')) const resolvedSize = sizeToSize(size) - const resolvedHtmlType = htmlType ?? 'button' + const resolvedHtmlType = nativeType ?? 'button' const isIconOnly = Boolean(icon) && !children const classes = cn( From 7e754d69166ebf33564b6bbb7830cfcfcbb2d04b Mon Sep 17 00:00:00 2001 From: Roberto Di Lillo Date: Thu, 5 Feb 2026 16:21:25 +0100 Subject: [PATCH 3/5] fix: simplify button variant api Remove danger/color props in favor of variant-only usage, and update size mapping and docs to match the new API. --- packages/core/USAGE.md | 8 ++--- packages/core/src/components/button/index.tsx | 35 +++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/core/USAGE.md b/packages/core/USAGE.md index d23679d..4c0ac30 100644 --- a/packages/core/USAGE.md +++ b/packages/core/USAGE.md @@ -59,8 +59,8 @@ function MyComponent() { import { Button } from '@mining-sdk/core' // Variants - - + + @@ -68,7 +68,7 @@ import { Button } from '@mining-sdk/core' // Sizes - + ``` @@ -169,7 +169,7 @@ function AlertDialogDemo() { return ( - + diff --git a/packages/core/src/components/button/index.tsx b/packages/core/src/components/button/index.tsx index f2e8727..e6ff8e0 100644 --- a/packages/core/src/components/button/index.tsx +++ b/packages/core/src/components/button/index.tsx @@ -6,35 +6,28 @@ export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'outline' | 'gh export type ButtonSize = 'sm' | 'md' | 'lg' | 'icon' export type ButtonIconPosition = 'left' | 'right' export type ButtonAntdSize = 'small' | 'middle' | 'large' -export type ButtonColor = 'primary' | 'secondary' | 'danger' | 'ghost' | 'link' | 'outline' - export type ButtonProps = { variant?: ButtonVariant size?: ButtonSize | ButtonAntdSize - danger?: boolean loading?: boolean icon?: React.ReactNode iconPosition?: ButtonIconPosition fullWidth?: boolean block?: boolean - color?: ButtonColor } & React.ButtonHTMLAttributes -function sizeToSize(size?: string): ButtonSize { - switch (size) { - case 'sm': - case 'small': - return 'sm' - case 'lg': - case 'large': - return 'lg' - case 'icon': - return 'icon' - case 'md': - case 'middle': - default: - return 'md' - } +const sizeMap: Record = { + sm: 'sm', + small: 'sm', + md: 'md', + middle: 'md', + lg: 'lg', + large: 'lg', + icon: 'icon', +} + +function sizeToSize(size?: ButtonSize | ButtonAntdSize): ButtonSize { + return size ? sizeMap[size] : 'md' } /** @@ -53,13 +46,11 @@ const Button = React.forwardRef( className, variant, size = 'md', - danger, loading, icon, iconPosition = 'left', fullWidth, block, - color, disabled, type: nativeType, children, @@ -67,7 +58,7 @@ const Button = React.forwardRef( }, ref, ) => { - const resolvedVariant = variant ?? (danger ? 'danger' : (color ?? 'secondary')) + const resolvedVariant = variant ?? 'secondary' const resolvedSize = sizeToSize(size) const resolvedHtmlType = nativeType ?? 'button' const isIconOnly = Boolean(icon) && !children From 1250e0dd361fe2ad280625822d00738f6b568db5 Mon Sep 17 00:00:00 2001 From: Roberto Di Lillo Date: Thu, 5 Feb 2026 16:22:12 +0100 Subject: [PATCH 4/5] style: simplify core token names Rename core tokens to remove button-specific prefixes and update button styles to reference the new variables. --- packages/core/src/components/button/styles.scss | 10 +++++----- packages/core/src/styles.scss | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/button/styles.scss b/packages/core/src/components/button/styles.scss index 5e13e93..d237d62 100644 --- a/packages/core/src/components/button/styles.scss +++ b/packages/core/src/components/button/styles.scss @@ -6,15 +6,15 @@ display: inline-flex; align-items: center; justify-content: center; - border-radius: var(--mdk-button-radius); - font-size: var(--mdk-button-font-size); - line-height: var(--mdk-button-line-height); - font-weight: var(--mdk-button-font-weight); + border-radius: var(--mdk-radius); + font-size: var(--mdk-font-size); + line-height: var(--mdk-line-height); + font-weight: var(--mdk-font-weight); gap: 0.5rem; border: 1px solid transparent; cursor: pointer; transition: colors 0.2s; - box-shadow: var(--mdk-button-shadow); + box-shadow: var(--mdk-shadow); &:focus-visible { outline: none; diff --git a/packages/core/src/styles.scss b/packages/core/src/styles.scss index e50bcd4..3dd9d6b 100644 --- a/packages/core/src/styles.scss +++ b/packages/core/src/styles.scss @@ -57,11 +57,11 @@ --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-button-radius: 0px; - --mdk-button-font-size: 14px; - --mdk-button-line-height: 22px; - --mdk-button-font-weight: 600; - --mdk-button-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.04); + --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; From 8d497ee3b228b3156578643861c1a6d9695d5d1d Mon Sep 17 00:00:00 2001 From: Roberto Di Lillo Date: Thu, 5 Feb 2026 16:47:48 +0100 Subject: [PATCH 5/5] feat: add select/dropdown components and demo Introduce MDK Select and Dropdown APIs with styling tokens, plus demo usage. --- apps/demo/src/App.scss | 16 + apps/demo/src/App.tsx | 73 ++ .../src/components/dropdown-menu/index.tsx | 227 +++++++ .../src/components/dropdown-menu/styles.scss | 81 +++ packages/core/src/components/select/index.tsx | 621 +++++++++++++++++- .../core/src/components/select/styles.scss | 235 +++++++ packages/core/src/index.ts | 3 +- packages/core/src/styles.scss | 25 + 8 files changed, 1279 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/components/dropdown-menu/styles.scss create mode 100644 packages/core/src/components/select/styles.scss diff --git a/apps/demo/src/App.scss b/apps/demo/src/App.scss index 67d361b..e9abb4f 100644 --- a/apps/demo/src/App.scss +++ b/apps/demo/src/App.scss @@ -94,6 +94,22 @@ 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 { diff --git a/apps/demo/src/App.tsx b/apps/demo/src/App.tsx index 5fd3547..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

@@ -73,6 +96,56 @@ function App(): JSX.Element {
+ {/* 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 3dd9d6b..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 @@ -77,6 +79,29 @@ --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 {