@opendevstack/ods-selector-widget
v0.0.2
Published
A flexible and customizable web component for displaying platform navigation and resource links
Keywords
Readme
ODS Selector Widget
A flexible and customizable web component for displaying platform navigation and resource links. Built with Stencil, this component provides a clean interface for organizing and accessing multiple platform resources in a project-based context.
Features
- 🎨 Fully Customizable: Extensive CSS custom properties for theming
- 🔌 Framework Agnostic: Works both with Angular and React
- 🔄 Dynamic Content: Fetches platform data from a service URL
- 🦴 Loading States: Built-in skeleton loader and error handling
- 🔗 Platform Selector: Dedicated platform icons with enable/disable states
- 📑 Sectioned Layout: Organize links into logical sections with tooltips
- 🔀 Multiple Link Types: Support for platform, project (internal), and external links with appropriate icons
Table of Contents
Installation
NPM
npm install @opendevstack/ods-selector-widgetUsage
Angular
1. Import the Component
For Angular with NgModule (traditional setup):
In your app.module.ts:
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { defineCustomElements } from '@opendevstack/ods-selector-widget/loader';
import { AppComponent } from './app.component';
defineCustomElements(window);
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }For Angular with Standalone Components:
In your main.ts:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { defineCustomElements } from '@opendevstack/ods-selector-widget/loader';
defineCustomElements(window);
bootstrapApplication(AppComponent, {
providers: []
});In your app.component.ts:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// Component logic
}2. Configure Angular (if needed)
Note: If you encounter the error
✘ [ERROR] No loader is configured for ".map" files, you need to update yourangular.jsonconfiguration.
Add the following to your architect.build.options in angular.json:
{
"architect": {
"build": {
"options": {
"customWebpackConfig": {
"loader": {
".map": "empty"
}
}
}
}
}
}3. Use in Template
<ods-selector-widget
project="My Project"
service-url="https://api.example.com/platforms/{{project}}">
</ods-selector-widget>React
The following code demonstrates how to integrate the ODS Selector Widget into a React application with TypeScript.
import { defineCustomElements } from '@opendevstack/ods-selector-widget/loader';
// Call this once in your app initialization
defineCustomElements(window);
function App() {
return (
<div>
<ods-selector-widget
project="My Project"
service-url="https://api.example.com/platforms/${project}"
/>
</div>
);
}
export default App;Key Points:
defineCustomElements(window): Registers the custom web component with the browser. This must be called once before rendering any<ods-selector-widget>elements.projectattribute: Sets the project name for the widget.service-urlattribute: Defines the API endpoint. Use${project}as a template variable that gets replaced with the project name.
For better TypeScript support in React:
To enable TypeScript support for the custom element, add this to your type declarations file:
// Add to your declarations file (e.g., custom-elements.d.ts)
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'ods-selector-widget': {
project?: string;
'service-url'?: string;
};
}
}
}Configure React + Vite (if needed)
Note: If you encounter the error
No loader is configured for ".map" filesin a React + Vite project, update yourvite.config.jsorvite.config.tsto exclude.mapfiles from dependency optimization.
Add the following to your Vite config:
// vite.config.js or vite.config.ts
export default {
optimizeDeps: {
exclude: ['*.map']
}
}Properties
| Property | Attribute | Type | Default | Description |
|----------|-----------|------|---------|-------------|
| project | project | string | undefined | The project name displayed in the widget header |
| serviceUrl | service-url | string | undefined | The service URL to fetch platform data. Use ${project} as a placeholder that will be replaced with the project name |
Example
<ods-selector-widget
project="PROJECT1"
service-url="https://api.myapp.com/platforms/${project}">
</ods-selector-widget>API Response Format
The component expects the service URL to return JSON data in the following format:
{
"sections": [
{
"section": "Select your platform",
"tooltip": "",
"links": [
{
"label": "Platform One",
"url": "https://platform1.com",
"abbreviation": "P1",
"type": "platform"
},
{
"label": "Platform Two",
"url": "https://platform2.com",
"abbreviation": "P2",
"type": "platform",
"disabled": true
},
{
"label": "Platform Three",
"url": "https://platform3.com",
"abbreviation": "P3",
"type": "platform",
"disabled": true
}
]
},
{
"section": "Documentation",
"tooltip": "Access project documentation and guides",
"links": [
{
"label": "API Reference",
"url": "https://docs.example.com/api",
"tooltip": "Complete API documentation",
"type": "external"
},
{
"label": "Getting Started",
"url": "https://docs.example.com/getting-started",
"tooltip": "Quick start guide",
"type": "project"
}
]
},
{
"section": "Tools",
"tooltip": "Development and monitoring tools",
"links": [
{
"label": "Dashboard",
"url": "https://dashboard.example.com",
"tooltip": "Project dashboard",
"type": "external"
}
]
}
]
}API Response Schema
- sections (
array): Array of content sections to display. Each section can contain either platform links or regular resource links.section(string): Section titletooltip(string, optional): Tooltip text for the section headerlinks(array): Array of links in this sectionlabel(string): Link display texturl(string): Link destination URLtype(string): Link type -"platform","project", or"external""platform": Renders as platform selector icons (when all links in a section are platform type)"project": Renders as list item with internal link arrow icon"external": Renders as list item with external link squared arrow icon
tooltip(string, optional): Tooltip text for the link (shown on hover for non-platform links)abbreviation(string, optional): Short text shown in platform icon (required fortype: "platform")disabled(boolean, optional): Iftrue, the platform link is disabled and shown as non-clickable (only applicable fortype: "platform")
Rendering Behavior
The component automatically adapts its rendering based on the link types within each section:
Platform Section: If all links in a section have
type: "platform", they are rendered as clickable platform icons in a horizontal layout. This is typically used for the main platform selector section.Link List Section: If a section contains any non-platform links (
type: "project"ortype: "external"), all links in that section are rendered as a vertical list with appropriate arrow icons split in two columns.
Example:
{
"sections": [
{
"section": "Platforms",
"links": [
{ "label": "Platform 1", "url": "...", "abbreviation": "P1", "type": "platform" },
{ "label": "Platform 2", "url": "...", "abbreviation": "P2", "type": "platform" }
]
}
]
}The example above renders as platform icons
{
"sections": [
{
"section": "Resources",
"links": [
{ "label": "Documentation", "url": "...", "type": "external" },
{ "label": "Dashboard", "url": "...", "type": "project" }
]
}
]
}The example above renders as a list with arrow/squared arrow icons.
Styling
The component uses CSS custom properties (CSS variables) for easy theming. You can override these in your stylesheet:
Available CSS Custom Properties
ods-selector-widget {
/* Font family */
--ods-selector-widget-font-family: 'Inter', sans-serif;
/* Primary colors */
--ods-selector-widget-color-primary: #08312A;
--ods-selector-widget-color-white: #FFF;
--ods-selector-widget-color-border: #5C5B59;
--ods-selector-widget-color-shadow: rgba(0, 0, 0, 0.05);
--ods-selector-widget-color-shadow-hover: rgba(0, 0, 0, 0.25);
/* Header colors */
--ods-selector-widget-header-bg: #CCFAE5;
--ods-selector-widget-header-text: #08312A;
/* Platform icon colors */
--ods-selector-widget-platform-icon-bg: #08312A;
--ods-selector-widget-platform-icon-text: white;
--ods-selector-widget-first-platform-bg: #008E4C;
--ods-selector-widget-first-platform-text: #ffffff;
--ods-selector-widget-second-platform-bg: #5E71FF;
--ods-selector-widget-second-platform-text: #ffffff;
--ods-selector-widget-third-platform-bg: #00C86B;
--ods-selector-widget-third-platform-text: #ffffff;
/* Disabled state colors */
--ods-selector-widget-disabled-bg: #E5E3DE;
--ods-selector-widget-disabled-text: #898885;
/* Section colors */
--ods-selector-widget-section-text: #08312A;
--ods-selector-widget-divider-color: #D3D3D3;
/* Link colors */
--ods-selector-widget-link-text: #08312A;
--ods-selector-widget-link-hover: #6B8375;
/* Error section colors */
--ods-selector-widget-error-text: #051D19;
--ods-selector-widget-error-button-border: #08312A;
--ods-selector-widget-error-button-text: #08312A;
--ods-selector-widget-error-button-hover-text: #6B8375;
--ods-selector-widget-error-button-hover-border: #6B8375;
/* Tooltip colors */
--ods-selector-widget-tooltip-bg: #051D19;
--ods-selector-widget-tooltip-text: #fff;
/* Skeleton loader colors */
--ods-selector-widget-skeleton-bg: #F6F5F3;
--ods-selector-widget-skeleton-shimmer: #e4e3e2;
}Example: Custom Theme
ods-selector-widget {
--ods-selector-widget-font-family: "YourCustomFont", sans-serif;
--ods-selector-widget-color-primary: #2563eb;
--ods-selector-widget-header-bg: #dbeafe;
--ods-selector-widget-header-text: #1e40af;
--ods-selector-widget-first-platform-bg: #10b981;
--ods-selector-widget-second-platform-bg: #8b5cf6;
--ods-selector-widget-third-platform-bg: #f59e0b;
}Publishing to npm
Prerequisites
- An npmjs.com account with access to the
@opendevstackscope. - You must be logged in to the npm registry:
npm login
Steps
1. Build the package
The dist/ and loader/ directories (declared in the files field of package.json) are generated by the build step and must be up to date before publishing.
npm run build2. Bump the version
Update the version field in package.json following Semantic Versioning, then record the change in CHANGELOG.md.
# patch: bug fixes → 0.2.0 → 0.2.1
npm version patch
# minor: new backwards-compatible features → 0.2.0 → 0.3.0
npm version minor
# major: breaking changes → 0.2.0 → 1.0.0
npm version majornpm version automatically updates package.json and creates a git tag.
3. Run tests
Make sure all tests pass before publishing.
npm test4. Publish
The package is scoped to @opendevstack, so you need to explicitly opt in to publishing it as a public package:
npm publish --access publicWhat gets published
Only the contents of the files array in package.json are included in the tarball:
| Directory | Contents |
|-----------|----------|
| dist/ | Compiled component bundles, type declarations, and collection manifest |
| loader/ | Custom Elements loader for lazy-loading in Angular/React apps |
You can preview the exact file list without publishing by running:
npm pack --dry-runDevelopment
- Node.js (v14 or higher)
- npm (v6 or higher)
Setup
# Clone the repository
git clone https://github.com/opendevstack/ods-selector-widget.git
cd ods-selector-widget
# Install dependencies
npm installAvailable Scripts
# Start development server with live reload
npm start
# Build for production
npm run build
# Run tests
npm test
# Run tests in watch mode
npm run test.watch
# Generate a new component
npm run generateProject Structure
ods-selector-widget/
├── src/
│ ├── components/
│ │ └── ods-selector-widget/
│ │ ├── ods-selector-widget.tsx # Component logic
│ │ ├── ods-selector-widget.css # Component styles
│ │ ├── ods-selector-widget.spec.ts # Unit tests
│ │ └── ods-selector-widget.e2e.ts # E2E tests
│ ├── index.html # Demo page
│ └── index.ts # Entry point
├── dist/ # Build output
├── www/ # Dev server output
└── stencil.config.ts # Stencil configurationTesting
The component includes both unit and end-to-end tests:
# Run all tests
npm test
# Run only unit tests
npm run test -- --spec
# Run only e2e tests
npm run test -- --e2e
# Generate coverage report
npm test -- --coverageBrowser Support
This component works in all modern browsers that support:
- Custom Elements V1
- Shadow DOM
- ES Modules
For older browsers, you may need to include polyfills.
Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Contribution Guidelines
- Write clear, descriptive commit messages
- Add tests for new features
- Update documentation as needed
- Follow the existing code style
- Ensure all tests pass before submitting PR
License
This project is licensed under the Apache License - see the LICENSE file for details.
Support
If you encounter any issues or have questions:
- Open an issue on GitHub Issues
- Check the Stencil documentation
Acknowledgments
Built with Stencil - A toolchain for building reusable, scalable Design Systems.
