refactor(frontend-dev): use published @lilith/ui-* packages
Replace local settings components with published packages: - @lilith/ui-forms@1.1.0: LabeledSlider (as Slider), TagInput (as EditableTagList), WeightSlider - @lilith/ui-primitives@1.2.0: SeverityBadge - @lilith/ui-feedback@1.1.0: PillTabs (as TabGroup) Other changes: - Add PUT method to api client - Remove unused imports across pages - Fix type safety in settings hooks - Keep PatternCard as local component (application-specific) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3ce3b744a6
commit
82a305edb7
18 changed files with 2259 additions and 1017 deletions
|
|
@ -22,9 +22,10 @@
|
|||
"@lilith/react-hooks": "workspace:*",
|
||||
"@lilith/react-query-utils": "workspace:*",
|
||||
"@lilith/ui-data": "^1.0.0",
|
||||
"@lilith/ui-feedback": "^1.0.0",
|
||||
"@lilith/ui-feedback": "^1.1.0",
|
||||
"@lilith/ui-forms": "^1.1.0",
|
||||
"@lilith/ui-messaging": "^1.0.2",
|
||||
"@lilith/ui-primitives": "^1.0.0",
|
||||
"@lilith/ui-primitives": "^1.2.0",
|
||||
"@lilith/ui-theme": "^1.0.0",
|
||||
"@lilith/ui-utils": "^1.0.1",
|
||||
"@tanstack/react-query": "^5.17.0",
|
||||
|
|
|
|||
2638
features/conversation-assistant/frontend-dev/pnpm-lock.yaml
generated
2638
features/conversation-assistant/frontend-dev/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -47,6 +47,12 @@ export const api = {
|
|||
body: body ? JSON.stringify(body) : undefined,
|
||||
}),
|
||||
|
||||
put: <T>(endpoint: string, body?: unknown) =>
|
||||
request<T>(endpoint, {
|
||||
method: 'PUT',
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
}),
|
||||
|
||||
delete: <T>(endpoint: string) =>
|
||||
request<T>(endpoint, { method: 'DELETE' }),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ export function useUpdateStyleProfile() {
|
|||
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<StyleProfile>) =>
|
||||
api.put<{ success: boolean; data: StyleProfile }>(`/api/settings/style-profile/${CREATOR_ID}`, data).then(r => r.data),
|
||||
api.put<StyleProfile>(`/api/settings/style-profile/${CREATOR_ID}`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['settings', 'style-profile'] });
|
||||
},
|
||||
|
|
@ -670,7 +670,7 @@ export function useUpdateRedFlagPattern() {
|
|||
|
||||
return useMutation({
|
||||
mutationFn: (data: { patternName: string; customWeight?: number | null; isEnabled?: boolean }) =>
|
||||
api.put<{ success: boolean; data: RedFlagPattern }>(`/api/settings/red-flags/${CREATOR_ID}/pattern`, data).then(r => r.data),
|
||||
api.put<RedFlagPattern>(`/api/settings/red-flags/${CREATOR_ID}/pattern`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['settings', 'red-flags'] });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
TrendingUp,
|
||||
TrendingDown,
|
||||
AlertTriangle,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
XCircle,
|
||||
Loader2,
|
||||
|
|
|
|||
|
|
@ -1,209 +0,0 @@
|
|||
import { useState, KeyboardEvent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { X, Plus } from 'lucide-react';
|
||||
|
||||
interface EditableTagListProps {
|
||||
tags: string[];
|
||||
onChange: (tags: string[]) => void;
|
||||
placeholder?: string;
|
||||
maxTags?: number;
|
||||
suggestions?: string[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
`;
|
||||
|
||||
const TagsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const Tag = styled.span`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px;
|
||||
background-color: ${(props) => props.theme.colors?.hover?.surface || '#374151'};
|
||||
border: 1px solid ${(props) => props.theme.colors?.border || '#4b5563'};
|
||||
border-radius: 16px;
|
||||
font-size: 13px;
|
||||
color: ${(props) => props.theme.colors?.text || '#ffffff'};
|
||||
`;
|
||||
|
||||
const RemoveButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: ${(props) => props.theme.colors?.text?.secondary || '#9ca3af'};
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.colors?.error || '#ef4444'};
|
||||
}
|
||||
`;
|
||||
|
||||
const InputRow = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid ${(props) => props.theme.colors?.border || '#374151'};
|
||||
background-color: ${(props) => props.theme.colors?.hover?.surface || '#374151'};
|
||||
color: ${(props) => props.theme.colors?.text || '#ffffff'};
|
||||
font-size: 14px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: ${(props) => props.theme.colors?.text?.secondary || '#6b7280'};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const AddButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 8px 16px;
|
||||
background-color: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const Suggestions = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
`;
|
||||
|
||||
const SuggestionChip = styled.button`
|
||||
padding: 4px 10px;
|
||||
background-color: transparent;
|
||||
border: 1px dashed ${(props) => props.theme.colors?.border || '#374151'};
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
color: ${(props) => props.theme.colors?.text?.secondary || '#9ca3af'};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
color: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
border-style: solid;
|
||||
}
|
||||
`;
|
||||
|
||||
export function EditableTagList({
|
||||
tags,
|
||||
onChange,
|
||||
placeholder = 'Add item...',
|
||||
maxTags = 20,
|
||||
suggestions = [],
|
||||
disabled = false,
|
||||
}: EditableTagListProps) {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const addTag = (tag: string) => {
|
||||
const trimmed = tag.trim();
|
||||
if (trimmed && !tags.includes(trimmed) && tags.length < maxTags) {
|
||||
onChange([...tags, trimmed]);
|
||||
setInputValue('');
|
||||
}
|
||||
};
|
||||
|
||||
const removeTag = (index: number) => {
|
||||
onChange(tags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addTag(inputValue);
|
||||
}
|
||||
};
|
||||
|
||||
const availableSuggestions = suggestions.filter((s) => !tags.includes(s));
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TagsContainer>
|
||||
{tags.map((tag, index) => (
|
||||
<Tag key={index}>
|
||||
{tag}
|
||||
{!disabled && (
|
||||
<RemoveButton onClick={() => removeTag(index)} aria-label={`Remove ${tag}`}>
|
||||
<X size={14} />
|
||||
</RemoveButton>
|
||||
)}
|
||||
</Tag>
|
||||
))}
|
||||
</TagsContainer>
|
||||
|
||||
<InputRow>
|
||||
<Input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled || tags.length >= maxTags}
|
||||
/>
|
||||
<AddButton
|
||||
onClick={() => addTag(inputValue)}
|
||||
disabled={disabled || !inputValue.trim() || tags.length >= maxTags}
|
||||
>
|
||||
<Plus size={16} />
|
||||
Add
|
||||
</AddButton>
|
||||
</InputRow>
|
||||
|
||||
{availableSuggestions.length > 0 && (
|
||||
<Suggestions>
|
||||
{availableSuggestions.slice(0, 8).map((suggestion) => (
|
||||
<SuggestionChip
|
||||
key={suggestion}
|
||||
onClick={() => addTag(suggestion)}
|
||||
disabled={disabled}
|
||||
>
|
||||
+ {suggestion}
|
||||
</SuggestionChip>
|
||||
))}
|
||||
</Suggestions>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import styled from 'styled-components';
|
||||
import { ToggleLeft, ToggleRight, Trash2 } from 'lucide-react';
|
||||
import { SeverityBadge } from './SeverityBadge';
|
||||
import { WeightSlider } from './WeightSlider';
|
||||
import { SeverityBadge } from '@lilith/ui-primitives';
|
||||
import { WeightSlider } from '@lilith/ui-forms';
|
||||
import type { RedFlagPattern } from '../../api/hooks';
|
||||
|
||||
interface PatternCardProps {
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import styled from 'styled-components';
|
||||
import type { RedFlagSeverity } from '../../api/hooks';
|
||||
|
||||
interface SeverityBadgeProps {
|
||||
severity: RedFlagSeverity;
|
||||
size?: 'small' | 'medium';
|
||||
}
|
||||
|
||||
const Badge = styled.span<{ $severity: RedFlagSeverity; $size: 'small' | 'medium' }>`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: ${(props) => (props.$size === 'small' ? '2px 6px' : '4px 10px')};
|
||||
border-radius: 4px;
|
||||
font-size: ${(props) => (props.$size === 'small' ? '10px' : '12px')};
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
background-color: ${(props) => {
|
||||
switch (props.$severity) {
|
||||
case 'CRITICAL':
|
||||
return 'rgba(239, 68, 68, 0.2)';
|
||||
case 'HIGH':
|
||||
return 'rgba(249, 115, 22, 0.2)';
|
||||
case 'MEDIUM':
|
||||
return 'rgba(234, 179, 8, 0.2)';
|
||||
case 'LOW':
|
||||
return 'rgba(34, 197, 94, 0.2)';
|
||||
}
|
||||
}};
|
||||
|
||||
color: ${(props) => {
|
||||
switch (props.$severity) {
|
||||
case 'CRITICAL':
|
||||
return '#ef4444';
|
||||
case 'HIGH':
|
||||
return '#f97316';
|
||||
case 'MEDIUM':
|
||||
return '#eab308';
|
||||
case 'LOW':
|
||||
return '#22c55e';
|
||||
}
|
||||
}};
|
||||
`;
|
||||
|
||||
export function SeverityBadge({ severity, size = 'medium' }: SeverityBadgeProps) {
|
||||
return (
|
||||
<Badge $severity={severity} $size={size}>
|
||||
{severity}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
interface SliderProps {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
leftLabel?: string;
|
||||
rightLabel?: string;
|
||||
showValue?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const SliderRow = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`;
|
||||
|
||||
const SliderLabel = styled.span`
|
||||
font-size: 12px;
|
||||
color: ${(props) => props.theme.colors?.text?.secondary || '#9ca3af'};
|
||||
min-width: 60px;
|
||||
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
const SliderInput = styled.input<{ $fillPercent: number }>`
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: ${(props) => {
|
||||
const fill = props.theme.colors?.primary || '#3b82f6';
|
||||
const track = props.theme.colors?.border || '#374151';
|
||||
return `linear-gradient(to right, ${fill} 0%, ${fill} ${props.$fillPercent}%, ${track} ${props.$fillPercent}%, ${track} 100%)`;
|
||||
}};
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const ValueDisplay = styled.span`
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.colors?.text || '#ffffff'};
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export function Slider({
|
||||
value,
|
||||
onChange,
|
||||
min = 0,
|
||||
max = 1,
|
||||
step = 0.1,
|
||||
leftLabel,
|
||||
rightLabel,
|
||||
showValue = true,
|
||||
disabled = false,
|
||||
}: SliderProps) {
|
||||
const fillPercent = ((value - min) / (max - min)) * 100;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SliderRow>
|
||||
{leftLabel && <SliderLabel>{leftLabel}</SliderLabel>}
|
||||
<SliderInput
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||
disabled={disabled}
|
||||
$fillPercent={fillPercent}
|
||||
/>
|
||||
{rightLabel && <SliderLabel>{rightLabel}</SliderLabel>}
|
||||
{showValue && <ValueDisplay>{(value * 100).toFixed(0)}%</ValueDisplay>}
|
||||
</SliderRow>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
interface Tab {
|
||||
id: string;
|
||||
label: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
interface TabGroupProps {
|
||||
tabs: Tab[];
|
||||
activeTab: string;
|
||||
onTabChange: (tabId: string) => void;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
background-color: ${(props) => props.theme.colors?.hover?.surface || '#374151'};
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
`;
|
||||
|
||||
const TabButton = styled.button<{ $active: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
background-color: ${(props) => (props.$active ? props.theme.colors?.surface || '#1f2937' : 'transparent')};
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: ${(props) => (props.$active ? 500 : 400)};
|
||||
color: ${(props) => (props.$active ? props.theme.colors?.text || '#ffffff' : props.theme.colors?.text?.secondary || '#9ca3af')};
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.colors?.text || '#ffffff'};
|
||||
}
|
||||
`;
|
||||
|
||||
const Count = styled.span<{ $active: boolean }>`
|
||||
padding: 2px 6px;
|
||||
background-color: ${(props) => (props.$active ? props.theme.colors?.primary || '#3b82f6' : 'rgba(255, 255, 255, 0.1)')};
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
export function TabGroup({ tabs, activeTab, onTabChange }: TabGroupProps) {
|
||||
return (
|
||||
<Container role="tablist">
|
||||
{tabs.map((tab) => (
|
||||
<TabButton
|
||||
key={tab.id}
|
||||
$active={activeTab === tab.id}
|
||||
onClick={() => onTabChange(tab.id)}
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.id}
|
||||
>
|
||||
{tab.label}
|
||||
{tab.count !== undefined && <Count $active={activeTab === tab.id}>{tab.count}</Count>}
|
||||
</TabButton>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
interface WeightSliderProps {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
defaultValue?: number;
|
||||
disabled?: boolean;
|
||||
showReset?: boolean;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`;
|
||||
|
||||
const SliderWrapper = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const SliderInput = styled.input<{ $fillPercent: number; $color: string }>`
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: ${(props) => {
|
||||
const track = props.theme.colors?.border || '#374151';
|
||||
return `linear-gradient(to right, ${props.$color} 0%, ${props.$color} ${props.$fillPercent}%, ${track} ${props.$fillPercent}%, ${track} 100%)`;
|
||||
}};
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: ${(props) => props.$color};
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: ${(props) => props.$color};
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const ValueDisplay = styled.span<{ $color: string }>`
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.$color};
|
||||
min-width: 45px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const ResetButton = styled.button<{ $visible: boolean }>`
|
||||
padding: 4px 8px;
|
||||
background: transparent;
|
||||
border: 1px solid ${(props) => props.theme.colors?.border || '#374151'};
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: ${(props) => props.theme.colors?.text?.secondary || '#9ca3af'};
|
||||
cursor: pointer;
|
||||
opacity: ${(props) => (props.$visible ? 1 : 0)};
|
||||
pointer-events: ${(props) => (props.$visible ? 'auto' : 'none')};
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
color: ${(props) => props.theme.colors?.primary || '#3b82f6'};
|
||||
}
|
||||
`;
|
||||
|
||||
function getWeightColor(value: number): string {
|
||||
if (value >= 0.8) return '#ef4444'; // Red for critical
|
||||
if (value >= 0.6) return '#f97316'; // Orange for high
|
||||
if (value >= 0.4) return '#eab308'; // Yellow for medium
|
||||
return '#22c55e'; // Green for low
|
||||
}
|
||||
|
||||
export function WeightSlider({
|
||||
value,
|
||||
onChange,
|
||||
defaultValue,
|
||||
disabled = false,
|
||||
showReset = true,
|
||||
}: WeightSliderProps) {
|
||||
const fillPercent = value * 100;
|
||||
const color = getWeightColor(value);
|
||||
const hasChanged = defaultValue !== undefined && value !== defaultValue;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<SliderWrapper>
|
||||
<SliderInput
|
||||
type="range"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
value={value}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||
disabled={disabled}
|
||||
$fillPercent={fillPercent}
|
||||
$color={color}
|
||||
/>
|
||||
<ValueDisplay $color={color}>{value.toFixed(2)}</ValueDisplay>
|
||||
</SliderWrapper>
|
||||
{showReset && defaultValue !== undefined && (
|
||||
<ResetButton
|
||||
$visible={hasChanged}
|
||||
onClick={() => onChange(defaultValue)}
|
||||
disabled={disabled}
|
||||
>
|
||||
Reset
|
||||
</ResetButton>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,16 @@
|
|||
export { Slider } from './Slider';
|
||||
export { EditableTagList } from './EditableTagList';
|
||||
export { WeightSlider } from './WeightSlider';
|
||||
export { SeverityBadge } from './SeverityBadge';
|
||||
// Re-export from @lilith packages
|
||||
export { LabeledSlider as Slider } from '@lilith/ui-forms';
|
||||
export { TagInput as EditableTagList } from '@lilith/ui-forms';
|
||||
export { WeightSlider } from '@lilith/ui-forms';
|
||||
export { SeverityBadge } from '@lilith/ui-primitives';
|
||||
export { PillTabs as TabGroup } from '@lilith/ui-feedback';
|
||||
|
||||
// Export types
|
||||
export type { LabeledSliderProps as SliderProps } from '@lilith/ui-forms';
|
||||
export type { TagInputProps as EditableTagListProps } from '@lilith/ui-forms';
|
||||
export type { WeightSliderProps } from '@lilith/ui-forms';
|
||||
export type { SeverityBadgeProps, SeverityLevel } from '@lilith/ui-primitives';
|
||||
export type { PillTabsProps as TabGroupProps, PillTab as Tab } from '@lilith/ui-feedback';
|
||||
|
||||
// Local component (application-specific)
|
||||
export { PatternCard } from './PatternCard';
|
||||
export { TabGroup } from './TabGroup';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft, User, Clock, Bot } from 'lucide-react';
|
||||
import styled from 'styled-components';
|
||||
import { Spinner, Button } from '@lilith/ui-primitives';
|
||||
import { Spinner } from '@lilith/ui-primitives';
|
||||
import {
|
||||
useContact,
|
||||
useClassificationHistory,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useRef, useEffect, useMemo, useCallback } from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { User, Users, ChevronRight } from 'lucide-react';
|
||||
import styled from 'styled-components';
|
||||
import { Spinner, Avatar } from '@lilith/ui-primitives';
|
||||
import { Spinner } from '@lilith/ui-primitives';
|
||||
import { formatRelativeTime } from '@lilith/ui-utils';
|
||||
import { useConversationsInfinite } from '../api/hooks';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from 'react';
|
||||
import { Laptop, Smartphone, Loader2, RefreshCw } from 'lucide-react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { Spinner, Button, StatusBadge } from '@lilith/ui-primitives';
|
||||
import { Spinner } from '@lilith/ui-primitives';
|
||||
import { useDevices, useDeactivateDevice, useResetDeviceSync } from '../api/hooks';
|
||||
|
||||
const spin = keyframes`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { Spinner, Button, Select } from '@lilith/ui-primitives';
|
||||
import { useTrainingSamples, useTrainingJobs, useStartTrainingJob } from '../api/hooks';
|
||||
import { useToast } from '@lilith/react-hooks';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import styled from 'styled-components';
|
|||
import { ArrowLeft, Loader2, AlertTriangle, MessageSquare, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { TabGroup, SeverityBadge } from '../../components/settings';
|
||||
import { useRedFlagDocumentation, type RedFlagDoc, type RedFlagCategory } from '../../api/hooks';
|
||||
import { useRedFlagDocumentation, type RedFlagDoc } from '../../api/hooks';
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 900px;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import styled from 'styled-components';
|
|||
import { ArrowLeft, Plus, Loader2 } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useToast } from '@lilith/react-hooks';
|
||||
import { TabGroup, PatternCard, SeverityBadge } from '../../components/settings';
|
||||
import { TabGroup, PatternCard } from '../../components/settings';
|
||||
import {
|
||||
useRedFlagPatterns,
|
||||
useUpdateRedFlagPattern,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue