Skip to content

Commit

Permalink
feat(select): implement component
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Jul 29, 2024
1 parent 00972d8 commit 5194e3a
Show file tree
Hide file tree
Showing 42 changed files with 3,038 additions and 56 deletions.
1 change: 1 addition & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"color-hex-length": null,
"color-named": "never",
"hue-degree-notation": "number",
"no-descending-specificity": null,
"scss/at-mixin-argumentless-call-parentheses": null,
"scss/at-rule-conditional-no-parentheses": null,
"scss/double-slash-comment-empty-line-before": null,
Expand Down
3 changes: 2 additions & 1 deletion packages/ods/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
},
"dependencies": {
"@floating-ui/dom": "1.6.3",
"@stencil/core": "4.12.2"
"@stencil/core": "4.12.2",
"tom-select": "2.3.1"
},
"devDependencies": {
"@jest/types": "29.6.3",
Expand Down
1 change: 1 addition & 0 deletions packages/ods/react/tests/_app/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const componentNames = [
'message',
'radio',
'checkbox',
'select',
//--generator-anchor--
];

Expand Down
35 changes: 35 additions & 0 deletions packages/ods/react/tests/_app/src/components/ods-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react-dom/client';
import { OdsSelect } from 'ods-components-react';

const Select = () => {
function onOdsChange() {
console.log('React select odsChange');
}

return (
<>
<OdsSelect name="react-select"
onOdsChange={ onOdsChange }>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="hamster">Hamster</option>
<option value="parrot">Parrot</option>
<option value="spider">Spider</option>
<option value="goldfish">Goldfish</option>
</OdsSelect>

<OdsSelect isDisabled={ true }
name="react-select"
onOdsChange={ onOdsChange }>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="hamster">Hamster</option>
<option value="parrot">Parrot</option>
<option value="spider">Spider</option>
<option value="goldfish">Goldfish</option>
</OdsSelect>
</>
);
};

export default Select;
50 changes: 50 additions & 0 deletions packages/ods/react/tests/e2e/ods-select.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Page } from 'puppeteer';
import { goToComponentPage, setupBrowser } from '../setup';

describe('ods-select react', () => {
const setup = setupBrowser();
let page: Page;

beforeAll(async () => {
page = setup().page;
});

beforeEach(async () => {
await goToComponentPage(page, 'ods-select');
});

it('render the component correctly', async () => {
const elem = await page.$('ods-select');
const boundingBox = await elem?.boundingBox();

expect(boundingBox?.height).toBeGreaterThan(0);
expect(boundingBox?.width).toBeGreaterThan(0);
});

it('trigger the odsChange handler on option selection', async () => {
const control = await page.$('ods-select:not([is-disabled]) >>> .ts-control');
let consoleLog = '';
page.on('console', (consoleObj) => {
consoleLog = consoleObj.text();
});

await control?.click();

const option = await page.$('ods-select:not([is-disabled]) >>> .option');
await option?.click();

// Small delay to ensure page console event has been resolved
await new Promise((resolve) => setTimeout(resolve, 100));

expect(consoleLog).toBe('React select odsChange');
});

it('does not open the option list if disabled', async () => {
const control = await page.$('ods-select[is-disabled] >>> .ts-control');

await control?.click();
const option = await page.$('ods-select:not([is-disabled]) >>> .option');

expect(option).toBeNull();
});
});
1 change: 1 addition & 0 deletions packages/ods/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export * from './form-field/src';
export * from './message/src';
export * from './radio/src';
export * from './checkbox/src';
export * from './select/src';
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
}
}

/* stylelint-disable no-descending-specificity */
&__clearable {
color: var(--ods-color-neutral-600);

Expand Down Expand Up @@ -72,7 +71,6 @@
}
}
}
/* stylelint-enable no-descending-specificity */

&__spinner {
padding-right: input.$ods-input-actions-padding-right;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export class OdsPopover {
arrow: this.arrowElement,
popper: this.el,
trigger: this.triggerElement,
}, {
offset: 8,
shift: { padding: 5 },
});

this.isOpen = true;
Expand Down
5 changes: 5 additions & 0 deletions packages/ods/src/components/select/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Local Stencil command generates external ods component build at the root of the project
# Excluding them is a temporary solution to avoid pushing generated files
# But the issue may cause main build (ods-component package) to fails, as it detects multiples occurences
# of the same component and thus you have to delete all those generated dir manually
*/src/
19 changes: 19 additions & 0 deletions packages/ods/src/components/select/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@ovhcloud/ods-component-select",
"version": "17.1.0",
"private": true,
"description": "ODS Select component",
"main": "dist/index.cjs.js",
"collection": "dist/collection/collection-manifest.json",
"scripts": {
"clean": "rimraf .stencil coverage dist docs-api www",
"doc": "typedoc --pretty --plugin ../../../scripts/typedoc-plugin-decorator.js && node ../../../scripts/generate-typedoc-md.js",
"lint:scss": "stylelint 'src/components/**/*.scss'",
"lint:ts": "eslint '{src,tests}/**/*.{js,ts,tsx}'",
"start": "stencil build --dev --watch --serve",
"test:e2e": "stencil test --e2e --config stencil.config.ts",
"test:e2e:ci": "tsc --noEmit && stencil test --e2e --ci --runInBand --config stencil.config.ts",
"test:spec": "stencil test --spec --config stencil.config.ts --coverage",
"test:spec:ci": "tsc --noEmit && stencil test --config stencil.config.ts --spec --ci --coverage"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
@use '../../../../../style/checkbox';
@use '../../../../../style/focus';
@use '../../../../../style/icon';
@use '../../../../../style/input';
@use '../../../../../style/overlay';
@import '~tom-select/dist/css/tom-select.bootstrap4.min.css';

$select-background-color: #fff;
$select-caret-size: 24px;
$select-base-padding: 8px;
$select-padding-right: $select-caret-size + ($select-base-padding * 2);

@mixin ods-select-item() {
color: var(--ods-color-text);
}

@mixin ods-select-placeholder() {
color: var(--ods-color-neutral-600);
}

:host(.ods-select) {
.ts-control {
border: solid 1px var(--ods-color-neutral-300);
border-radius: var(--ods-border-radius-sm);
background-color: $select-background-color;
cursor: pointer !important;
padding: 0 $select-padding-right 0 $select-base-padding !important;
height: input.$ods-input-input-height;
min-height: auto;

.item {
@include ods-select-item();
}
}

.disabled {
.ts-control {
@include input.ods-input-disabled();

opacity: 1;
cursor: not-allowed !important;

* {
cursor: not-allowed !important;
color: inherit !important;
}
}
}

.dropdown-active.focus {
.ts-control {
box-shadow: none;
}
}

:not(.dropdown-active).focus {
.ts-control {
@include focus.ods-focus();

box-shadow: none;
}
}

.ts-dropdown {
z-index: overlay.$ods-overlay-z-index - 1;
margin: 0;
box-shadow: none;

&.ods-select__dropdown--bottom {
border-radius: 0 0 var(--ods-border-radius-sm) var(--ods-border-radius-sm);
}

&.ods-select__dropdown--top {
border-radius: var(--ods-border-radius-sm) var(--ods-border-radius-sm) 0 0;
}
}

.ts-dropdown-content {
padding: 0;

.option {
display: flex;
align-items: center;
padding: 0 8px;
min-height: input.$ods-input-input-height;
color: var(--ods-color-text);

&:not([data-selectable]) {
@include input.ods-input-disabled();

opacity: 1;
}

&.active,
&:hover {
background-color: var(--ods-color-primary-100);
}
}

.optgroup {
&::before {
margin: 0;
}

&[data-disabled] {
@include input.ods-input-disabled();

opacity: 1;
}

.optgroup-header {
display: flex;
align-items: center;
background-color: inherit;
padding: 0 8px;
min-height: input.$ods-input-input-height;
color: var(--ods-color-heading);
font-size: 1rem;
font-weight: 600;
}

.option {
padding-left: 24px;
}
}

.tomselect-checkbox {
@include checkbox.ods-checkbox();

margin-right: 8px;
}
}

.ts-placeholder {
@include ods-select-placeholder();
}

.plugin-merge_selected_items {
.ts-merged-items {
@include ods-select-item();

margin: 0;
background: $select-background-color;
cursor: pointer;
padding: 0;
}

.ts-merged-items-placeholder {
@include ods-select-placeholder();
}
}

.ts-wrapper {
&.multi,
&.single {
.ts-control {
&::after {
@include icon.ods-icon();

position: absolute;
top: 50%;
right: $select-base-padding;
transform: translateY(-50%);
margin: 0;
border: none;
background-color: inherit;
width: auto;
height: auto;
color: var(--ods-color-primary-500);
font-size: $select-caret-size;
content: '\e916';
}
}

&.dropdown-active {
.ts-control {
&::after {
content: '\e919';
}
}
}

&.disabled {
.ts-control {
&::after {
color: var(--ods-color-neutral-600);
}
}

.ts-merged-items {
@include input.ods-input-disabled();
}
}
}

&.multi {
&.plugin-placeholder {
.ts-placeholder {
// As we use the merge plugin, we enforce the placeholder from other plugin to be hidden
display: none !important;
}
}
}
}
}

// Double class to give more weight than base override
:host(.ods-select.ods-select--error) {
.ts-control {
@include input.ods-input-error();
}
}
Loading

0 comments on commit 5194e3a

Please sign in to comment.