Skip to content

Commit

Permalink
feat(clipboard): implement component
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Jul 29, 2024
1 parent b6f37cb commit 205fcd2
Show file tree
Hide file tree
Showing 38 changed files with 1,459 additions and 39 deletions.
20 changes: 2 additions & 18 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,10 @@
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
// "ignorePatterns": [
// "**/.yarn/**",
// "**/assets/**",
// "**/docs/**",
// "**/packages/**/dist/**",
// "**/packages/**/doc/**",
// "**/packages/themes/**",
//// "**/packages/components/**/custom-elements",
//// "**/packages/components/**/custom-elements-bundle",
//// "**/packages/components/**/documentation",
//// "**/packages/components/**/loader",
//// "**/packages/components/**/react",
//// "**/packages/components/**/vue",
// "**/packages/**/www",
// "**/*.d.ts"
// ],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"project": ["tsconfig.json", "tsconfig.test.json"],
"project": ["tsconfig.json"],
"sourceType": "module"
},
"plugins": [
Expand All @@ -39,7 +23,7 @@
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "h" }],
"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true, "varsIgnorePattern": "h" }],
"@typescript-eslint/ban-ts-comment": ["error", { "ts-ignore": "allow-with-description" }],
"array-bracket-spacing": ["error", "never"],
"arrow-parens": ["error", "always"],
Expand Down
1 change: 1 addition & 0 deletions packages/ods/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.DS_Store
node_modules/
dist/
tests/coverage/

# Generated doc file
spec.md
Expand Down
17 changes: 17 additions & 0 deletions packages/ods/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getJestOption } from './src/config/jest';

export default getJestOption({
args: [],
option: {
collectCoverageFrom: [
'<rootDir>/src/!(components)/**/*.{ts,tsx}',
],
coverageDirectory: '<rootDir>/tests/coverage',
testPathIgnorePatterns: [
'/node_modules/',
'dist/',
'scripts/',
'src/components/',
],
},
});
7 changes: 6 additions & 1 deletion packages/ods/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
"build:stencil": "stencil build --prod --config stencil.config.ts",
"build:style": "npm --prefix style run build",
"build:vue": "npm --prefix vue run build",
"lint:scss": "stylelint 'src/style/*.scss'"
"lint:scss": "stylelint 'src/style/*.scss'",
"lint:ts": "eslint '{src,tests}/!(components)/**/*.{ts,tsx}'",
"test:spec": "jest --config jest.config.ts --coverage",
"test:spec:ci": "npm run test:spec"
},
"dependencies": {
"@floating-ui/dom": "1.6.3",
Expand All @@ -37,6 +40,7 @@
"autoprefixer": "10.4.17",
"concurrently": "8.2.2",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-puppeteer": "10.0.1",
"jest-transform-stub": "2.0.0",
"postcss": "8.4.35",
Expand All @@ -45,6 +49,7 @@
"sass": "1.71.0",
"stencil-inline-svg": "1.1.0",
"ts-jest": "29.1.2",
"ts-node": "10.9.2",
"typedoc": "0.25.11",
"typescript": "5.3.3",
"wait-on": "7.2.0"
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 @@ -23,6 +23,7 @@ const componentNames = [
'textarea',
'breadcrumb',
'toggle',
'clipboard',
//--generator-anchor--
];

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

const Clipboard = () => {
return (
<OdsClipboard />
);
};

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

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

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

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

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

expect(boundingBox?.height).toBeGreaterThan(0);
expect(boundingBox?.width).toBeGreaterThan(0);
});
});
5 changes: 5 additions & 0 deletions packages/ods/src/components/clipboard/.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/clipboard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@ovhcloud/ods-component-clipboard",
"version": "17.1.0",
"private": true,
"description": "ODS Clipboard 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 --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,43 @@
@use '../../../../../style/input';

:host(.ods-clipboard) {
display: inline-block;
position: relative;
height: input.$ods-input-input-height;
}

.ods-clipboard {
&__copy {
display: flex;
position: absolute;
top: 0;
right: input.$ods-input-actions-padding-right;
bottom: 0;
align-items: center;
justify-content: center;

&::part(button) {
padding: input.$ods-input-actions-padding-right;
width: input.$ods-input-actions-button-width;
height: input.$ods-input-actions-button-width;
}
}

&__tooltip {
margin: 0;
font-size: 0.75rem;
font-weight: 600;

&__copied {
display: flex;
flex-flow: row;
column-gap: 4px;
align-items: center;
justify-content: center;

&__check {
font-size: 1.125rem;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Component, Element, Event, type EventEmitter, type FunctionalComponent, Host, Method, Prop, State, h } from '@stencil/core';
import { copyToClipboard, getRandomHTMLId } from '../../../../../utils/dom';
import { ODS_BUTTON_VARIANT } from '../../../../button/src';
import { ODS_ICON_NAME } from '../../../../icon/src';

@Component({
shadow: true,
styleUrl: 'ods-clipboard.scss',
tag: 'ods-clipboard',
})
export class OdsClipboard {
private copyButtonId = 'clipboard-copy';
private hostId: string = '';

@Element() el!: HTMLElement;

@State() isCopyDone: boolean = false;

@Prop({ reflect: true }) public isDisabled: boolean = false;
@Prop({ reflect: true }) public labelCopy: string = 'Copy to clipboard';
@Prop({ reflect: true }) public labelCopySuccess: string = 'Copied!';
@Prop({ reflect: true }) public value?: string;

@Event() odsClipboardCopy!: EventEmitter<string>;

@Method()
async copy(): Promise<void> {
if (this.isDisabled) {
return;
}

await copyToClipboard(this.value || '');

this.isCopyDone = true;
this.odsClipboardCopy.emit(this.value);
}

componentWillLoad(): void {
this.hostId = this.el.id || getRandomHTMLId();
}

onTooltipHide(): void {
this.isCopyDone = false;
}

render(): FunctionalComponent {
return (
<Host
class="ods-clipboard"
id={ this.hostId }>
<ods-input
class="ods-clipboard__input"
isDisabled={ this.isDisabled }
isReadonly={ true }
name=""
value={ this.value }>
</ods-input>

<ods-button
class="ods-clipboard__copy"
icon={ ODS_ICON_NAME.filesCopy }
id={ this.copyButtonId }
isDisabled={ this.isDisabled }
label=""
onClick={ () => this.copy() }
variant={ ODS_BUTTON_VARIANT.ghost }>
</ods-button>

{
!this.isDisabled &&
<ods-tooltip
onOdsTooltipHide={ () => this.onTooltipHide() }
position="right"
shadowDomTriggerId={ this.copyButtonId }
triggerId={ this.hostId }>
<p class="ods-clipboard__tooltip">
{
this.isCopyDone
? <span class="ods-clipboard__tooltip__copied">
{ this.labelCopySuccess }

<ods-icon
class="ods-clipboard__tooltip__copied__check"
name={ ODS_ICON_NAME.check }>
</ods-icon>
</span>
: this.labelCopy
}
</p>
</ods-tooltip>
}
</Host>
);
}
}
11 changes: 11 additions & 0 deletions packages/ods/src/components/clipboard/src/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Import here all the external ODS component that you need to run the current component
* when running dev server (yarn start) or e2e tests
*
* ex:
* import '../../text/src';
*/
import '../../button/src';
import '../../icon/src';
import '../../input/src';
import '../../tooltip/src';
58 changes: 58 additions & 0 deletions packages/ods/src/components/clipboard/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html dir='ltr' lang='en'>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0' />
<title>Dev ods-clipboard</title>

<script type='module' src='/build/ods-clipboard.esm.js'></script>
<script nomodule src='/build/ods-clipboard.js'></script>
<link rel="stylesheet" href="/build/ods-clipboard.css">
</head>

<body>
<p>Default</p>
<ods-clipboard>
</ods-clipboard>

<p>Custom host id</p>
<ods-clipboard id="my-clipboard">
</ods-clipboard>

<p>Disabled</p>
<ods-clipboard id="disabled-clipboard" value="Disabled" is-disabled>
</ods-clipboard>
<button onclick="toggleDisable()">Toggle disable state</button>
<script>
const disabledClipboard = document.querySelector('#disabled-clipboard');

function toggleDisable() {
if (disabledClipboard.getAttribute('is-disabled') !== null) {
disabledClipboard.removeAttribute('is-disabled')
} else {
disabledClipboard.setAttribute('is-disabled', '');
}
}
</script>

<p>Custom labels</p>
<ods-clipboard label-copy="Copier le contenu" label-copy-success="Copié !">
</ods-clipboard>

<p>Value</p>
<ods-clipboard value="Some content to copy">
</ods-clipboard>

<p>Event</p>
<ods-clipboard id="event-clipboard" value="Event copied content">
</ods-clipboard>
<script>
const eventClipboard = document.querySelector('#event-clipboard');

eventClipboard.addEventListener('odsClipboardCopy', (e) => {
console.log('odsClipboardCopy event received');
console.log(e);
});
</script>
</body>
</html>
1 change: 1 addition & 0 deletions packages/ods/src/components/clipboard/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { OdsClipboard } from './components/ods-clipboard/ods-clipboard';
7 changes: 7 additions & 0 deletions packages/ods/src/components/clipboard/stencil.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getStencilConfig } from '../../config/stencil';

export const config = getStencilConfig({
args: process.argv.slice(2),
componentCorePackage: '@ovhcloud/ods-component-clipboard',
namespace: 'ods-clipboard',
});
Loading

0 comments on commit 205fcd2

Please sign in to comment.