diff --git a/apps/demo-app/src/app/app.html b/apps/demo-app/src/app/app.html index b459336..053bd17 100644 --- a/apps/demo-app/src/app/app.html +++ b/apps/demo-app/src/app/app.html @@ -1,9 +1,5 @@ -
-

Blender UI Demo

-

Current Theme: {{ activeTheme().name }}

- + -
This is a panel.
- - -
+ diff --git a/apps/demo-app/src/app/app.scss b/apps/demo-app/src/app/app.scss index 9404d1d..00c1b00 100644 --- a/apps/demo-app/src/app/app.scss +++ b/apps/demo-app/src/app/app.scss @@ -4,29 +4,31 @@ display: block; width: 100vw; height: 100vh; - background-color: bui.$bui-bg-dark; - color: bui.$bui-text-color; - padding: 20px; + margin: 0; + padding: 0; + overflow: hidden; font-family: sans-serif; } -.panel { - background-color: bui.$bui-panel-bg; - padding: 20px; - border-radius: bui.$bui-border-radius; - margin-top: 20px; +.theme-toggle { + position: fixed; + bottom: 20px; + right: 20px; + width: 40px; + height: 40px; + border-radius: 50%; + background-color: bui.$bui-bg-light; + color: bui.$bui-text-color; border: 1px solid bui.$bui-border-dark; -} - -button { - padding: 10px 20px; - background-color: bui.$bui-primary; - color: white; - border: none; - border-radius: bui.$bui-border-radius; + display: flex; + align-items: center; + justify-content: center; cursor: pointer; + z-index: 1000; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); + padding: 0; &:hover { - opacity: 0.9; + background-color: bui.$bui-panel-bg; } } diff --git a/apps/demo-app/src/app/app.spec.ts b/apps/demo-app/src/app/app.spec.ts index 4270b7b..2724306 100644 --- a/apps/demo-app/src/app/app.spec.ts +++ b/apps/demo-app/src/app/app.spec.ts @@ -1,20 +1,24 @@ import { TestBed } from '@angular/core/testing'; import { App } from './app'; -import { NxWelcome } from './nx-welcome'; describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [App, NxWelcome], + imports: [App], }).compileComponents(); }); - it('should render title', async () => { + it('should render modifier panel', async () => { const fixture = TestBed.createComponent(App); - await fixture.whenStable(); + fixture.detectChanges(); const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain( - 'Welcome demo-app', - ); + expect(compiled.querySelector('bui-modifier-panel')).toBeTruthy(); + }); + + it('should render theme toggle', async () => { + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('.theme-toggle')).toBeTruthy(); }); }); diff --git a/apps/demo-app/src/app/app.ts b/apps/demo-app/src/app/app.ts index 1275ad7..3e15bff 100644 --- a/apps/demo-app/src/app/app.ts +++ b/apps/demo-app/src/app/app.ts @@ -1,9 +1,9 @@ import { Component, inject, computed } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { BuiThemeService } from '@blender-ui/core'; +import { BuiThemeService, ModifierPanelComponent } from '@blender-ui/core'; @Component({ - imports: [RouterModule], + imports: [RouterModule, ModifierPanelComponent], selector: 'app-root', templateUrl: './app.html', styleUrl: './app.scss', diff --git a/demo-app.log b/demo-app.log index df2e0b7..fae9495 100644 --- a/demo-app.log +++ b/demo-app.log @@ -1,14 +1,34 @@ -> nx run demo-app:serve:development --port=4200 +> nx run demo-app:serve:development โฏ Building... โœ” Building... Initial chunk files | Names  |  Raw size -main.js  | main  |  7.80 kB |  +main.js  | main  |  73.04 kB |  styles.css  | styles  | 110 bytes |  -   | Initial total |  7.91 kB -Application bundle generation complete. [3.360 seconds] - 2026-02-22T22:28:04.671Z +   | Initial total |  73.15 kB +Application bundle generation complete. [4.012 seconds] - 2026-02-22T23:47:13.515Z  Watch mode enabled. Watching for file changes... NOTE: Raw file sizes do not reflect development server per-request transformations. โžœ Local: http://localhost:4200/ +โฏ Changes detected. Rebuilding... +โœ” Changes detected. Rebuilding... +Initial chunk files | Names | Raw size +main.js  | main  | 73.11 kB |  +Application bundle generation complete. [0.764 seconds] - 2026-02-22T23:50:56.104Z + +Page reload sent to client(s). +โฏ Changes detected. Rebuilding... +โœ” Changes detected. Rebuilding... + +No output file changes. +Application bundle generation complete. [2.666 seconds] - 2026-02-22T23:51:14.860Z + +โฏ Changes detected. Rebuilding... +โœ” Changes detected. Rebuilding... +Initial chunk files | Names | Raw size +main.js  | main  | 73.11 kB |  +Application bundle generation complete. [0.797 seconds] - 2026-02-22T23:51:31.420Z + +Component update sent to client(s). diff --git a/libs/blender-ui/src/index.ts b/libs/blender-ui/src/index.ts index afc596f..1551df5 100644 --- a/libs/blender-ui/src/index.ts +++ b/libs/blender-ui/src/index.ts @@ -1,3 +1,8 @@ export * from './lib/core/core'; export * from './lib/services/theme.service'; export * from './lib/services/themes'; +export * from './lib/components/checkbox/checkbox.component'; +export * from './lib/components/number-input/number-input.component'; +export * from './lib/components/select/select.component'; +export * from './lib/components/section/section.component'; +export * from './lib/components/modifier-panel/modifier-panel.component'; diff --git a/libs/blender-ui/src/lib/components/checkbox/checkbox.component.html b/libs/blender-ui/src/lib/components/checkbox/checkbox.component.html new file mode 100644 index 0000000..471b4d2 --- /dev/null +++ b/libs/blender-ui/src/lib/components/checkbox/checkbox.component.html @@ -0,0 +1,4 @@ +
+
+ {{ label() }} +
diff --git a/libs/blender-ui/src/lib/components/checkbox/checkbox.component.scss b/libs/blender-ui/src/lib/components/checkbox/checkbox.component.scss new file mode 100644 index 0000000..e1dbd44 --- /dev/null +++ b/libs/blender-ui/src/lib/components/checkbox/checkbox.component.scss @@ -0,0 +1,58 @@ +@use '../../styles/variables' as *; + +:host { + display: inline-block; +} + +.bui-checkbox-container { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + + &.disabled { + cursor: not-allowed; + opacity: 0.5; + } +} + +.bui-checkbox { + appearance: none; + background-color: transparent; + margin: 0; + width: 1rem; + height: 1rem; + border: 1px solid $bui-text-color; // Fallback or distinct color + border-radius: 2px; + display: grid; + place-content: center; + margin-right: 0.5rem; + transition: all 0.2s ease; + + // The checkmark + &::before { + content: ""; + width: 0.65em; + height: 0.65em; + transform: scale(0); + transition: 120ms transform ease-in-out; + box-shadow: inset 1em 1em white; + transform-origin: center; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + } + + &.checked { + background-color: $bui-primary; + border-color: $bui-primary; + + &::before { + transform: scale(1); + } + } +} + +.label { + font-family: var(--bui-font-display, "Inter", "system-ui", "sans-serif"); + font-size: 0.75rem; // 12px + color: $bui-text-color; +} diff --git a/libs/blender-ui/src/lib/components/checkbox/checkbox.component.ts b/libs/blender-ui/src/lib/components/checkbox/checkbox.component.ts new file mode 100644 index 0000000..54d4061 --- /dev/null +++ b/libs/blender-ui/src/lib/components/checkbox/checkbox.component.ts @@ -0,0 +1,21 @@ +import { Component, input, output } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'bui-checkbox', + standalone: true, + imports: [CommonModule], + templateUrl: './checkbox.component.html', + styleUrl: './checkbox.component.scss', +}) +export class BuiCheckboxComponent { + checked = input(false); + label = input(''); + disabled = input(false); + checkedChange = output(); + + toggle() { + if (this.disabled()) return; + this.checkedChange.emit(!this.checked()); + } +} diff --git a/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.html b/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.html new file mode 100644 index 0000000..4a3d87e --- /dev/null +++ b/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.html @@ -0,0 +1,148 @@ +
+ +
+
๐Ÿ”ง
+
+
๐Ÿ“ท
+
๐Ÿ–จ๏ธ
+
๐Ÿ“‘
+
โ˜€
+
๐ŸŒ
+
+
๐Ÿ”ง
+
๐Ÿ“
+
๐Ÿงช
+
๐Ÿ”—
+
๐Ÿ”บ
+
๐Ÿ”ด
+
๐Ÿ
+
+ + +
+ +
+ + โ€บ + +
+
๐Ÿ“Œ
+
+ + +
+ + + +
+ +
+ โ–ผ + ๐ŸŒ€ + Subdivision + +
+ + + + +
+ +
+ + +
+ + +
+ +
+
+ + +
+
+ + +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ + + +
+
+ +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+ + + + +
+
diff --git a/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.scss b/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.scss new file mode 100644 index 0000000..b7b6d4b --- /dev/null +++ b/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.scss @@ -0,0 +1,289 @@ +@use '../../styles/variables' as *; + +:host { + display: block; + height: 100vh; + width: 100vw; + overflow: hidden; + background-color: $bui-bg-dark; + color: $bui-text-color; + font-family: var(--bui-font-display, "Inter", "system-ui", "sans-serif"); + font-size: 12px; +} + +.bui-layout { + display: flex; + height: 100%; +} + +.bui-sidebar { + width: 2.5rem; // w-10 ~ 40px + background-color: $bui-panel-bg; // panel-light / dark + border-right: 1px solid $bui-border-dark; + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem 0; + gap: 0.25rem; + + .icon-button { + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: $bui-border-radius; + cursor: pointer; + opacity: 0.7; + + &:hover { + background-color: rgba(255,255,255,0.1); + opacity: 1; + } + + &.active { + background-color: $bui-highlight; // highlight-light/dark + color: #fff; + opacity: 1; + } + } + + .separator { + width: 100%; + height: 1px; + background-color: $bui-border-dark; + margin: 0.25rem 0; + } +} + +.bui-main { + flex: 1; + display: flex; + flex-direction: column; + background-color: $bui-panel-bg; +} + +.bui-panel-top { + height: 2rem; + display: flex; + align-items: center; + padding: 0 0.5rem; + border-bottom: 1px solid $bui-border-dark; + background-color: $bui-panel-bg; + + .breadcrumb-item { + display: flex; + align-items: center; + gap: 0.25rem; + cursor: pointer; + padding: 0.125rem 0.25rem; + border-radius: 2px; + + &:hover { + background-color: rgba(255,255,255,0.1); + } + } + + .separator { + margin: 0 0.5rem; + color: #888; + } + + .spacer { + flex: 1; + } +} + +.bui-panel-content { + flex: 1; + padding: 0.5rem; + overflow-y: auto; +} + +.add-modifier-btn { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 0.25rem 0.75rem; + margin-bottom: 0.75rem; + background-color: $bui-bg-light; // white / #3d3d3d + border: 1px solid $bui-border-dark; + border-radius: $bui-border-radius; + color: $bui-text-color; + cursor: pointer; + + &:hover { + background-color: rgba(255,255,255,0.05); + } +} + +.bui-modifier-card { + border: 1px solid $bui-primary; + border-radius: $bui-border-radius; + background-color: $bui-card-bg; + overflow: hidden; + box-shadow: 0 1px 2px rgba(0,0,0,0.1); +} + +.bui-modifier-header { + height: 1.75rem; + display: flex; + align-items: center; + padding: 0 0.5rem; + background-color: $bui-panel-header; // blue-50 / #343434 + border-bottom: 1px solid $bui-border-dark; + gap: 0.5rem; + user-select: none; + + .expand-icon { + font-size: 0.6rem; + color: #888; + cursor: pointer; + } + + .modifier-icon { + color: $bui-highlight; // blue-400 + } + + .modifier-title { + flex: 1; + font-weight: 600; + } + + .modifier-toggles { + display: flex; + gap: 2px; + + .toggle-btn { + padding: 0.125rem; + border-radius: 2px; + cursor: pointer; + border: none; + background: transparent; + color: #888; + + &:hover { + background-color: rgba(255,255,255,0.1); + } + + &.active { + color: $bui-highlight; // blue-400 + } + } + } + + .separator-vertical { + height: 1rem; + width: 1px; + background-color: $bui-border-dark; + margin: 0 0.25rem; + } + + .close-btn, .drag-btn { + background: transparent; + border: none; + color: #888; + cursor: pointer; + + &:hover { + color: #fff; + } + } + + .close-btn:hover { + color: #ff5555; + } +} + +.bui-modifier-body { + padding: 0.5rem; + display: flex; + flex-direction: column; + gap: 0.375rem; // space-y-1.5 +} + +.type-toggle-row { + display: flex; + margin-bottom: 0.5rem; + + .toggle-group { + display: flex; + width: 100%; + border: 1px solid $bui-border-dark; + border-radius: 2px; + overflow: hidden; + + .toggle-option { + flex: 1; + padding: 0.25rem 0.5rem; + text-align: center; + background-color: $bui-bg-light; // white / #222 + color: $bui-text-color; + border: none; + border-right: 1px solid $bui-border-dark; + cursor: pointer; + + &:last-child { + border-right: none; + } + + &:hover { + background-color: rgba(255,255,255,0.05); + } + + &.active { + background-color: $bui-primary; // highlight-light / primary + color: #fff; + } + } + } +} + +.control-row { + display: grid; + grid-template-columns: 110px 1fr 10px; // Adjusted for dot + gap: 0.25rem; + align-items: center; + + label { + text-align: right; + padding-right: 0.5rem; + color: #888; // text-gray-600/400 + } + + .status-dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background-color: #555; // gray-600 + + &.hidden { + opacity: 0; + } + } + + &.checkbox-row { + .spacer-label { + // empty + } + } +} + +.bui-footer { + height: 1.5rem; + background-color: $bui-panel-bg; + border-top: 1px solid $bui-border-dark; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 0.5rem; + font-size: 10px; + color: #888; + + .footer-left, .footer-right { + display: flex; + gap: 0.75rem; + align-items: center; + } +} diff --git a/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.ts b/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.ts new file mode 100644 index 0000000..28f57f9 --- /dev/null +++ b/libs/blender-ui/src/lib/components/modifier-panel/modifier-panel.component.ts @@ -0,0 +1,50 @@ +import { Component, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { BuiCheckboxComponent } from '../checkbox/checkbox.component'; +import { BuiNumberInputComponent } from '../number-input/number-input.component'; +import { BuiSelectComponent, SelectOption } from '../select/select.component'; +import { BuiSectionComponent } from '../section/section.component'; + +@Component({ + selector: 'bui-modifier-panel', + standalone: true, + imports: [ + CommonModule, + BuiCheckboxComponent, + BuiNumberInputComponent, + BuiSelectComponent, + BuiSectionComponent, + ], + templateUrl: './modifier-panel.component.html', + styleUrl: './modifier-panel.component.scss', +}) +export class ModifierPanelComponent { + // State for the inputs + levelsViewport = signal(1); + levelsRender = signal(2); + optimalDisplay = signal(true); + + // Advanced section + useLimitSurface = signal(true); + quality = signal(3); + uvSmooth = signal('Keep Boundaries'); + boundarySmooth = signal('All'); + useCreases = signal(true); + useCustomNormals = signal(false); + + // Options + uvSmoothOptions: SelectOption[] = [ + { label: 'Keep Boundaries', value: 'Keep Boundaries' }, + { label: 'None', value: 'None' }, + { label: 'Keep Corners', value: 'Keep Corners' }, + { label: 'Keep Corners, Junctions', value: 'Keep Corners, Junctions' }, + { label: 'Keep Corners, Junctions, Concave', value: 'Keep Corners, Junctions, Concave' }, + { label: 'Keep Boundaries, Junctions', value: 'Keep Boundaries, Junctions' }, + { label: 'All', value: 'All' }, + ]; + + boundarySmoothOptions: SelectOption[] = [ + { label: 'All', value: 'All' }, + { label: 'Keep Corners', value: 'Keep Corners' }, + ]; +} diff --git a/libs/blender-ui/src/lib/components/number-input/number-input.component.html b/libs/blender-ui/src/lib/components/number-input/number-input.component.html new file mode 100644 index 0000000..3769a9b --- /dev/null +++ b/libs/blender-ui/src/lib/components/number-input/number-input.component.html @@ -0,0 +1,16 @@ +
+
+ +
+ โ–ฒ + โ–ผ +
+
diff --git a/libs/blender-ui/src/lib/components/number-input/number-input.component.scss b/libs/blender-ui/src/lib/components/number-input/number-input.component.scss new file mode 100644 index 0000000..755258a --- /dev/null +++ b/libs/blender-ui/src/lib/components/number-input/number-input.component.scss @@ -0,0 +1,91 @@ +@use '../../styles/variables' as *; + +:host { + display: block; + width: 100%; +} + +.bui-number-input-container { + position: relative; + width: 100%; + height: 1.5rem; // ~24px + background-color: $bui-input-bg; + border: 1px solid $bui-border-dark; + border-radius: 2px; // rounded-sm + overflow: hidden; + + &:hover { + .progress-bar { + opacity: 0.5; // Slightly lighter on hover + } + + .bui-number-input { + color: #fff; // text-white + } + + .arrows { + opacity: 1; + } + } + + &:focus-within { + border-color: $bui-primary; + // ring-1 ring-primary + box-shadow: 0 0 0 1px $bui-primary; + } +} + +.progress-bar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.1); // gray-600/30 equivalent + pointer-events: none; + transition: width 0.1s ease; + z-index: 0; +} + +.bui-number-input { + width: 100%; + height: 100%; + background: transparent; + border: none; + color: $bui-text-color; + font-family: inherit; + font-size: 0.75rem; // xs + text-align: center; + position: relative; + z-index: 1; + padding: 0 0.5rem; + appearance: textfield; // remove spin buttons standard + + &:focus { + outline: none; + } + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } +} + +.arrows { + position: absolute; + right: 2px; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; + z-index: 2; + + .arrow-up, .arrow-down { + font-size: 6px; + line-height: 6px; + color: #888; + } +} diff --git a/libs/blender-ui/src/lib/components/number-input/number-input.component.ts b/libs/blender-ui/src/lib/components/number-input/number-input.component.ts new file mode 100644 index 0000000..83636da --- /dev/null +++ b/libs/blender-ui/src/lib/components/number-input/number-input.component.ts @@ -0,0 +1,31 @@ +import { Component, computed, input, output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'bui-number-input', + standalone: true, + imports: [CommonModule, FormsModule], + templateUrl: './number-input.component.html', + styleUrl: './number-input.component.scss', +}) +export class BuiNumberInputComponent { + value = input(0); + min = input(0); + max = input(100); + step = input(1); + valueChange = output(); + + progressWidth = computed(() => { + const val = this.value(); + const min = this.min(); + const max = this.max(); + const percentage = ((val - min) / (max - min)) * 100; + return `${Math.min(100, Math.max(0, percentage))}%`; + }); + + onInput(event: Event) { + const val = (event.target as HTMLInputElement).valueAsNumber; + this.valueChange.emit(val); + } +} diff --git a/libs/blender-ui/src/lib/components/section/section.component.html b/libs/blender-ui/src/lib/components/section/section.component.html new file mode 100644 index 0000000..f1e5d62 --- /dev/null +++ b/libs/blender-ui/src/lib/components/section/section.component.html @@ -0,0 +1,7 @@ +
+ โ–ถ + +
+
+ +
diff --git a/libs/blender-ui/src/lib/components/section/section.component.scss b/libs/blender-ui/src/lib/components/section/section.component.scss new file mode 100644 index 0000000..45b9bbb --- /dev/null +++ b/libs/blender-ui/src/lib/components/section/section.component.scss @@ -0,0 +1,44 @@ +@use '../../styles/variables' as *; + +:host { + display: block; + padding-top: 0.5rem; // pt-2 +} + +.bui-section-header { + display: flex; + align-items: center; + cursor: pointer; + color: #888; // text-gray-400 + font-size: 0.75rem; // xs + user-select: none; + + &:hover { + color: $bui-text-color; // hover:text-gray-200 + } +} + +.arrow-icon { + margin-right: 0.25rem; + font-size: 0.6rem; + transition: transform 0.1s ease; + display: inline-block; + + &.expanded { + transform: rotate(90deg); + } +} + +.section-label { + font-weight: 500; +} + +.bui-section-content { + padding-left: 0.5rem; // pl-2 + margin-top: 0.25rem; // mt-1 + border-left: 1px solid rgba(255, 255, 255, 0.1); // border-white/10 + margin-left: 0.375rem; // ml-1.5 + display: flex; + flex-direction: column; + gap: 0.375rem; // space-y-1.5 +} diff --git a/libs/blender-ui/src/lib/components/section/section.component.ts b/libs/blender-ui/src/lib/components/section/section.component.ts new file mode 100644 index 0000000..4799543 --- /dev/null +++ b/libs/blender-ui/src/lib/components/section/section.component.ts @@ -0,0 +1,18 @@ +import { Component, input, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'bui-section', + standalone: true, + imports: [CommonModule], + templateUrl: './section.component.html', + styleUrl: './section.component.scss', +}) +export class BuiSectionComponent { + label = input(''); + expanded = signal(false); + + toggle() { + this.expanded.update((v) => !v); + } +} diff --git a/libs/blender-ui/src/lib/components/select/select.component.html b/libs/blender-ui/src/lib/components/select/select.component.html new file mode 100644 index 0000000..7b02f06 --- /dev/null +++ b/libs/blender-ui/src/lib/components/select/select.component.html @@ -0,0 +1,6 @@ +
+ + โ–ผ +
diff --git a/libs/blender-ui/src/lib/components/select/select.component.scss b/libs/blender-ui/src/lib/components/select/select.component.scss new file mode 100644 index 0000000..7376c03 --- /dev/null +++ b/libs/blender-ui/src/lib/components/select/select.component.scss @@ -0,0 +1,39 @@ +@use '../../styles/variables' as *; + +:host { + display: block; + width: 100%; +} + +.bui-select-container { + position: relative; + width: 100%; +} + +.bui-select { + width: 100%; + appearance: none; + background-color: $bui-input-bg; + border: 1px solid $bui-border-dark; + border-radius: 2px; + color: $bui-text-color; + font-family: inherit; + font-size: 0.75rem; // xs + padding: 0.25rem 1.5rem 0.25rem 0.5rem; // Right padding for arrow + cursor: pointer; + + &:focus { + outline: none; + border-color: $bui-primary; + } +} + +.arrow-icon { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: #888; + font-size: 0.6rem; +} diff --git a/libs/blender-ui/src/lib/components/select/select.component.ts b/libs/blender-ui/src/lib/components/select/select.component.ts new file mode 100644 index 0000000..68ac9cc --- /dev/null +++ b/libs/blender-ui/src/lib/components/select/select.component.ts @@ -0,0 +1,29 @@ +import { Component, input, output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +export interface SelectOption { + label: string; + value: any; +} + +@Component({ + selector: 'bui-select', + standalone: true, + imports: [CommonModule, FormsModule], + templateUrl: './select.component.html', + styleUrl: './select.component.scss', +}) +export class BuiSelectComponent { + options = input([]); + value = input(null); + valueChange = output(); + + onChange(event: Event) { + const val = (event.target as HTMLSelectElement).value; + // Find the original value from options if needed, but for string values it's fine. + // If value is complex object, we might need to map it back. + // Assuming simple string/number values for now. + this.valueChange.emit(val); + } +} diff --git a/libs/blender-ui/src/lib/services/themes.ts b/libs/blender-ui/src/lib/services/themes.ts index 911b553..b965337 100644 --- a/libs/blender-ui/src/lib/services/themes.ts +++ b/libs/blender-ui/src/lib/services/themes.ts @@ -8,39 +8,47 @@ export interface Theme { export const blenderDark: Theme = { name: 'dark', properties: { - '--bui-primary': '#e77e22', - '--bui-bg-light': '#f0f0f0', + '--bui-primary': '#4772b3', + '--bui-bg-light': '#e0e0e0', '--bui-bg-dark': '#1d1d1d', - '--bui-panel-bg': '#282828', + '--bui-panel-bg': '#2b2b2b', '--bui-panel-header': '#303030', - '--bui-input-bg': '#232323', + '--bui-input-bg': '#181818', '--bui-input-active-green': '#55802b', '--bui-input-active-yellow': '#8c7f21', '--bui-input-text': '#dedede', '--bui-tab-bg': '#222222', '--bui-tab-active': '#3d3d3d', - '--bui-border-dark': '#181818', - '--bui-text-color': '#e2e2e2', + '--bui-border-dark': '#3d3d3d', + '--bui-text-color': '#cccccc', '--bui-border-radius': '4px', + '--bui-highlight': '#5680c2', + '--bui-surface-dark': '#303030', + '--bui-surface-light': '#f5f5f5', + '--bui-card-bg': '#303030', }, }; export const blenderLight: Theme = { name: 'light', properties: { - '--bui-primary': '#e77e22', - '--bui-bg-light': '#ffffff', - '--bui-bg-dark': '#f0f0f0', - '--bui-panel-bg': '#e6e6e6', - '--bui-panel-header': '#d9d9d9', + '--bui-primary': '#4772b3', + '--bui-bg-light': '#e0e0e0', + '--bui-bg-dark': '#1d1d1d', + '--bui-panel-bg': '#f0f0f0', + '--bui-panel-header': '#f5f5f5', '--bui-input-bg': '#ffffff', '--bui-input-active-green': '#7bc654', '--bui-input-active-yellow': '#d4c24a', '--bui-input-text': '#333333', '--bui-tab-bg': '#d9d9d9', '--bui-tab-active': '#e6e6e6', - '--bui-border-dark': '#cccccc', - '--bui-text-color': '#333333', + '--bui-border-dark': '#d1d1d1', + '--bui-text-color': '#222222', '--bui-border-radius': '4px', + '--bui-highlight': '#375e97', + '--bui-surface-dark': '#303030', + '--bui-surface-light': '#f5f5f5', + '--bui-card-bg': '#ffffff', }, }; diff --git a/libs/blender-ui/src/lib/styles/_variables.scss b/libs/blender-ui/src/lib/styles/_variables.scss index fbcc5c1..7e6f242 100644 --- a/libs/blender-ui/src/lib/styles/_variables.scss +++ b/libs/blender-ui/src/lib/styles/_variables.scss @@ -12,3 +12,7 @@ $bui-tab-active: var(--bui-tab-active); $bui-border-dark: var(--bui-border-dark); $bui-text-color: var(--bui-text-color); $bui-border-radius: var(--bui-border-radius); +$bui-highlight: var(--bui-highlight); +$bui-surface-dark: var(--bui-surface-dark); +$bui-surface-light: var(--bui-surface-light); +$bui-card-bg: var(--bui-card-bg); diff --git a/verification/modifier_panel.png b/verification/modifier_panel.png new file mode 100644 index 0000000..15a7f81 Binary files /dev/null and b/verification/modifier_panel.png differ diff --git a/verification/modifier_panel_expanded.png b/verification/modifier_panel_expanded.png new file mode 100644 index 0000000..d9543f0 Binary files /dev/null and b/verification/modifier_panel_expanded.png differ diff --git a/verification/verify_modifier_panel.py b/verification/verify_modifier_panel.py new file mode 100644 index 0000000..0c8ad85 --- /dev/null +++ b/verification/verify_modifier_panel.py @@ -0,0 +1,24 @@ +from playwright.sync_api import sync_playwright + +def verify_modifier_panel(): + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + page.goto("http://localhost:4200") + + # Wait for modifier panel + page.wait_for_selector("bui-modifier-panel") + + # Click on Advanced section to expand + page.click("text=Advanced") + + # Wait for expansion animation or just wait a bit + page.wait_for_timeout(500) + + # Take screenshot + page.screenshot(path="verification/modifier_panel_expanded.png") + + browser.close() + +if __name__ == "__main__": + verify_modifier_panel()