Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions DRAG_DROP_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# DataRowItem Drag and Drop Implementation

This implementation adds drag and drop functionality to reorder DataRowItem components using the colored status bar as a drag handle.

## Features

- **Drag Handle**: The colored status bar (red/green/blue div) on the left side serves as the drag handle
- **Visual Feedback**: Items show visual feedback during drag operations (opacity changes, drop target highlighting)
- **Smooth Animations**: Transitions and hover effects for better UX
- **Immediate Updates**: Order changes are applied immediately upon drop

## Usage

### 1. Basic Setup

Import the required utilities and components:

```typescript
import DataRowItem from '$lib/components/UI/dashboard/DataRowItem.svelte';
import { createDragState, createDragHandlers, type DragState } from '$lib/utilities/dragAndDrop';
```

### 2. Component State

Set up the drag state and handlers:

```typescript
// Your device data array
let devices = $state([/* your devices */]);

// Drag state
let dragState: DragState = $state(createDragState());

function updateDragState(newState: Partial<DragState>) {
dragState = { ...dragState, ...newState };
}

// Handle reordering
function handleDeviceReorder(newDevices: DeviceType[]) {
devices = newDevices;
// Optional: persist order to backend
}

// Create drag handlers
let dragHandlers = $derived(createDragHandlers(
devices,
handleDeviceReorder,
dragState,
updateDragState
));
```

### 3. Template Usage

Use DataRowItem with drag props:

```svelte
{#each devices as device, index (device.dev_eui)}
<DataRowItem
{device}
isActive={/* your active status logic */}
dragEnabled={true}
dragIndex={index}
isDragging={dragState.draggedIndex === index}
isDropTarget={dragState.dropTargetIndex === index}
onDragStart={dragHandlers.handleDragStart}
onDragEnd={dragHandlers.handleDragEnd}
onDragOver={dragHandlers.handleDragOver}
onDrop={dragHandlers.handleDrop}
/>
{/each}
```

### 4. Container Components

#### DeviceCards.svelte

For list view with individual device cards:

```svelte
<DeviceCards
{devices}
viewType="list"
enableDragAndDrop={true}
onDevicesReorder={(newDevices) => {
// Handle reordering
devices = newDevices;
}}
/>
```

#### AllDevices.svelte

For grouped devices by location:

```svelte
<AllDevices
{locations}
{deviceActiveStatus}
enableDragAndDrop={true}
onDeviceReorder={(locationId, newDevices) => {
// Handle reordering within location
const location = locations.find(l => l.location_id === locationId);
if (location) {
location.cw_devices = newDevices;
}
}}
/>
```

## Props Reference

### DataRowItem Drag Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `dragEnabled` | `boolean` | `false` | Enable/disable drag functionality |
| `dragIndex` | `number` | `undefined` | Index of item in the array |
| `isDragging` | `boolean` | `false` | Whether this item is being dragged |
| `isDropTarget` | `boolean` | `false` | Whether this item is a drop target |
| `onDragStart` | `function` | `undefined` | Drag start handler |
| `onDragEnd` | `function` | `undefined` | Drag end handler |
| `onDragOver` | `function` | `undefined` | Drag over handler |
| `onDrop` | `function` | `undefined` | Drop handler |

## Visual States

### Drag Handle
- **Normal**: Colored status bar with normal opacity
- **Hover**: Increased opacity and slight scale on hover (when drag enabled)
- **Dragging**: Grabbing cursor, scale animation

### Item States
- **Dragging**: 50% opacity
- **Drop Target**: Blue ring border and light blue background
- **Normal**: Default appearance

## Demo

See `src/lib/components/demo/DragDropDemo.svelte` for a working example.

## Technical Details

### Drag Data
- Uses `device.dev_eui` as the drag data identifier
- Effect allowed: `move`

### Event Handling
- Prevents default browser drag behavior
- Manages drag state through centralized handlers
- Updates arrays using immutable patterns

### Browser Compatibility
- Uses standard HTML5 Drag and Drop API
- Works in all modern browsers
- Fallback cursor states for better UX

## Customization

### Styling
The drag handle styling can be customized by modifying the classes in DataRowItem.svelte:

```svelte
<div
class="absolute top-0 bottom-0 left-0 my-1 w-1.5 rounded-full opacity-70 transition-all duration-200"
class:cursor-grab={dragEnabled}
class:hover:scale-125={dragEnabled}
<!-- Add your custom classes -->
>
```

### Persistence
Implement order persistence by adding backend calls in your reorder handlers:

```typescript
async function handleDeviceReorder(newDevices) {
devices = newDevices;

// Save order to backend
await fetch('/api/devices/reorder', {
method: 'POST',
body: JSON.stringify({
deviceOrder: newDevices.map(d => d.dev_eui)
})
});
}
```

## Troubleshooting

### Common Issues

1. **Drag not working**: Ensure `dragEnabled={true}` is set
2. **Visual feedback missing**: Check that drag state props are properly passed
3. **Order not updating**: Verify the reorder handler is updating the source array
4. **Performance issues**: Use proper key attributes in `{#each}` blocks

### Debug Tips

- Check browser console for drag event logs
- Verify drag state object updates
- Ensure unique keys for each item
- Test with browser dev tools drag simulation
60 changes: 60 additions & 0 deletions THEME.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Theme System

Centralized theme management supports three modes: `light`, `dark`, and `system`.

## How It Works
- User preference stored in `localStorage` under `theme.mode`.
- `themeStore` (in `src/lib/stores/theme.ts`) exposes `{ mode, effective, system }`.
- `effective` is the applied theme (`light` or `dark`). If `mode === 'system'`, it mirrors OS preference; otherwise user selection overrides OS.
- The `<html>` element gets the `dark` class when effective theme is dark, plus a `data-theme="light|dark"` attribute for additional hooks.

## API
```ts
import { themeStore, setThemeMode, toggleExplicitLightDark } from '$lib/stores/theme';
setThemeMode('dark'); // force dark
setThemeMode('light'); // force light
setThemeMode('system'); // follow OS
```

## UI Component: ThemeModeSelector
Use the built-in selector component for a user-facing control:
```svelte
<script lang="ts">
import ThemeModeSelector from '$lib/components/theme/ThemeModeSelector.svelte';
</script>

<ThemeModeSelector />
```
Desktop renders a segmented control; mobile falls back to a `<select>`. It updates the store and persists automatically.

## Adding Theme-aware Styles
Prefer CSS variables defined in `src/app.css`:
```css
background: var(--color-background);
color: var(--color-text);
```
Dark variants automatically applied when `.dark` is on `<html>`.

## Buttons
All buttons unified through `src/lib/components/ui/base/Button.svelte`.
Props:
- `variant`: `primary | secondary | ghost | outline | danger`
- `size`: `sm | md | lg`
- `href`: Navigates (SPA if internal)

Legacy button components now wrap the base component for backward compatibility.

## Migration Notes
- Avoid direct `matchMedia('(prefers-color-scheme: dark)')` checks; subscribe to `themeStore` instead.
- If you must react to theme changes in a component:
```ts
import { themeStore } from '$lib/stores/theme';
let theme;
const unsub = themeStore.subscribe(v => theme = v.effective);
```
- Remove any manual DOM class toggling (central store owns it).

## Future Enhancements
- Add high-contrast mode.
- Expose user-accessible palette customization.
- Persist expanded design tokens into Tailwind config for full JIT class generation.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
"globals": "^16.0.0",
"husky": "^8.0.0",
"jsdom": "^26.0.0",
"supabase": "^2.33.9",
"lint-staged": "^16.1.2",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"supabase": "^2.33.9",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
Expand All @@ -65,6 +65,7 @@
"vitest": "^3.0.0"
},
"dependencies": {
"@internationalized/date": "^3.9.0",
"@mdi/js": "^7.4.47",
"@stencil/store": "^2.1.3",
"@stripe/stripe-js": "^7.4.0",
Expand Down
11 changes: 7 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions scripts/download-notojp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Download NotoSansJP-Regular.ttf into static/fonts
set -euo pipefail
OUT_DIR="$(pwd)/static/fonts"
URL="https://github.com/googlefonts/noto-cjk/raw/main/Sans/TTF/Japanese/NotoSansJP-Regular.otf"
# Note: the repository contains .otf; PDFKit supports OTF too, but the code expects .ttf filename.
OUT_FILE="$OUT_DIR/NotoSansJP-Regular.otf"
mkdir -p "$OUT_DIR"
if command -v curl >/dev/null 2>&1; then
curl -L -o "$OUT_FILE" "$URL"
elif command -v wget >/dev/null 2>&1; then
wget -O "$OUT_FILE" "$URL"
else
echo "Install curl or wget to run this script." >&2
exit 1
fi
# Inform the user
echo "Downloaded $OUT_FILE"

echo "If you need a .ttf file instead of .otf, convert it using fonttools or obtain the .ttf distribution."
Loading