npm package: @lpti/widget-unpam-sso
Description: SSO Widget component for UNPAM SSO - React dropdown widget with iframe integration
Author: titanjenius
Latest version: 0.0.7

# Widget SSO

Embed the UNPAM SSO login widget as an iframe. Supports both vanilla JavaScript and React (JSX/TSX) usage. No dependencies required for vanilla JS. TypeScript types included for React.

## Installation

```bash
npm install @lpti/widget-unpam-sso
```

Or using yarn:

```bash
yarn add @lpti/widget-unpam-sso
```

## Usage

### Vanilla JavaScript (Native, No Dependencies)

```js
import { embedWidgetSSO } from '@lpti/widget-unpam-sso';

// Basic usage
embedWidgetSSO('sso-widget-container');

// With options
embedWidgetSSO('sso-widget-container', {
  env: 'prod',     // 'dev' or 'prod'
  lang: 'en',      // 'id' or 'en'
  size: 'lg',      // 'sm', 'md', or 'lg'
  theme: 'dark',   // 'light' or 'dark'
  logoUrl: '/path/to/logo.png',
  className: 'my-sso-widget'
});
```

### HTML Script Tag (via CDN)

```html
<script type="module">
  import { embedWidgetSSO } from 'https://unpkg.com/@lpti/widget-unpam-sso@latest/index.js';
  embedWidgetSSO('sso-widget-container', {
    env: 'prod',
    lang: 'id',
    size: 'md',
    theme: 'light'
  });
</script>
<div id="sso-widget-container"></div>
```

### React Component (JSX/TSX)

```jsx
import { WidgetSSO } from '@lpti/widget-unpam-sso/react';

function App() {
  return (
    <div className="app">
      {/* Basic usage */}
      <WidgetSSO />
      
      {/* With options */}
      <WidgetSSO 
        env="prod"
        lang="en"
        size="lg"
        theme="dark"
        logoUrl="/path/to/logo.png"
        className="my-sso-widget"
        onLoad={() => console.log('SSO widget loaded')}
        onError={error => console.error('SSO widget error:', error)}
      />
    </div>
  );
}
```

### React Hook Usage

```jsx
import { useWidgetSSO } from '@lpti/widget-unpam-sso/react';

function CustomSSOComponent() {
  const { iframeUrl, isLoading, hasError, reload } = useWidgetSSO({
    env: 'prod',
    lang: 'id',
    size: 'md',
    theme: 'light'
  });

  if (isLoading) return <div>Loading SSO widget...</div>;
  if (hasError) return <div>Error loading SSO widget. <button onClick={reload}>Retry</button></div>;

  return (
    <iframe
      src={iframeUrl}
      width="100%"
      height="40px"
      frameBorder="0"
      scrolling="no"
    />
  );
}
```

### Vue 3 Integration

```vue
<template>
  <div>
    <div ref="widgetContainer" class="widget-container"></div>
  </div>
</template>

<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { WidgetSSO } from '@lpti/widget-unpam-sso/index';

export default {
  name: 'SsoWidget',
  props: {
    theme: { type: String, default: 'light' },
    size: { type: String, default: 'md' },
    lang: { type: String, default: 'id' },
    env: { type: String, default: 'dev' },
    logoUrl: { type: String, default: null }
  },
  setup(props, { emit }) {
    const widgetContainer = ref(null);
    let ssoWidget = null;
    
    onMounted(() => {
      if (widgetContainer.value) {
        ssoWidget = new WidgetSSO({
          theme: props.theme,
          size: props.size,
          lang: props.lang,
          env: props.env,
          logoUrl: props.logoUrl
        });
        
        ssoWidget.render(widgetContainer.value);
        
        // Set up iframe load event handler
        const iframe = widgetContainer.value.querySelector('iframe');
        if (iframe) {
          iframe.onload = () => emit('load');
          iframe.onerror = (error) => emit('error', error);
        }
      }
    });
    
    onBeforeUnmount(() => {
      if (ssoWidget) {
        ssoWidget.destroy();
      }
    });
    
    return {
      widgetContainer
    };
  }
}
</script>
```

### Vue 2 Integration

```vue
<template>
  <div>
    <div ref="widgetContainer" class="widget-container"></div>
  </div>
</template>

<script>
import { WidgetSSO } from '@lpti/widget-unpam-sso/index';

export default {
  name: 'SsoWidget',
  props: {
    theme: { type: String, default: 'light' },
    size: { type: String, default: 'md' },
    lang: { type: String, default: 'id' },
    env: { type: String, default: 'dev' },
    logoUrl: { type: String, default: null }
  },
  data() {
    return {
      ssoWidget: null
    };
  },
  mounted() {
    if (this.$refs.widgetContainer) {
      this.ssoWidget = new WidgetSSO({
        theme: this.theme,
        size: this.size,
        lang: this.lang,
        env: this.env,
        logoUrl: this.logoUrl
      });
      
      this.ssoWidget.render(this.$refs.widgetContainer);
      
      // Set up iframe load event handler
      const iframe = this.$refs.widgetContainer.querySelector('iframe');
      if (iframe) {
        iframe.onload = () => this.$emit('load');
        iframe.onerror = (error) => this.$emit('error', error);
      }
    }
  },
  beforeDestroy() {
    if (this.ssoWidget) {
      this.ssoWidget.destroy();
    }
  }
}
</script>
```

### Vue 3 with Composition API (Alternative)

```js
// UseWidgetSSO.js - Composable
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { WidgetSSO } from '@lpti/widget-unpam-sso/index';

export function useWidgetSSO(options = {}) {
  const containerRef = ref(null);
  const isLoading = ref(true);
  const hasError = ref(false);
  let widget = null;

  const initWidget = () => {
    if (!containerRef.value) return;
    
    isLoading.value = true;
    hasError.value = false;
    
    try {
      widget = new WidgetSSO(options);
      widget.render(containerRef.value);
      
      const iframe = containerRef.value.querySelector('iframe');
      if (iframe) {
        iframe.onload = () => {
          isLoading.value = false;
        };
        iframe.onerror = () => {
          isLoading.value = false;
          hasError.value = true;
        };
      }
    } catch (error) {
      isLoading.value = false;
      hasError.value = true;
      console.error('Failed to initialize widget:', error);
    }
  };

  onMounted(() => {
    initWidget();
  });
  
  onBeforeUnmount(() => {
    if (widget) {
      widget.destroy();
    }
  });

  return {
    containerRef,
    isLoading,
    hasError,
    reload: initWidget
  };
}
```

## API

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `theme` | `'light' \| 'dark'` | `'light'` | Widget color theme |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Widget size |
| `lang` | `'id' \| 'en'` | `'id'` | Language code |
| `url` | `string` | `''` | Custom widget URL (overrides env) |
| `env` | `'dev' \| 'prod'` | `'dev'` | Environment to use |
| `logoUrl` | `string \| null` | `null` | URL to logo image |
| `className` | `string` | `''` | Custom class for the container |
| `buttonClassName` | `string` | `''` | Custom class for the button |
| `dropdownClassName` | `string` | `''` | Custom class for the dropdown |
| `style` | `object` | `{}` | Custom styles for the container |
| `buttonStyle` | `object` | `{}` | Custom styles for the button |
| `dropdownStyle` | `object` | `{}` | Custom styles for the dropdown |
| `width` | `string` | `'100%'` | CSS width of the widget |
| `height` | `string` | `'auto'` | CSS height of the widget |

### Vanilla JavaScript

#### `embedWidgetSSO(elementId, options)`

Embeds the SSO widget into a target element.

**Parameters:**
- `elementId` (string): The id of the target HTML element
- `options` (object, optional): Configuration options (see above)

### React Component

#### `<WidgetSSO />` Props

All the same options as the vanilla JS version, plus:
- `onLoad` (function): Callback when iframe loads successfully
- `onError` (function): Callback when iframe fails to load

#### `useWidgetSSO(options)` Hook

Returns an object with:
- `iframeUrl` (string): The generated iframe URL
- `isLoading` (boolean): Whether the URL is being validated
- `hasError` (boolean): Whether there was an error loading
- `reload` (function): Function to retry loading the URL

## Environments

- **Development**: `https://devsso.unpam.ac.id/widget`
- **Production**: `https://satu.unpam.ac.id/widget`

## Features

- ✅ No dependencies for vanilla JS
- ✅ Native JavaScript ES6 modules
- ✅ React component (JSX/TSX) and TypeScript support
- ✅ React hooks for advanced usage
- ✅ Vue 2 and Vue 3 integration
- ✅ Multiple language support (Indonesian/English)
- ✅ Theme support (light/dark)
- ✅ Responsive sizing options
- ✅ URL validation before iframe creation
- ✅ Error handling and logging
- ✅ Loading states for React components

## Browser Support

The widget supports all modern browsers (Chrome, Firefox, Safari, Edge).

## License

MIT