Skip to content

Commit e6ef14e

Browse files
committed
fix(skills): reuse shared upload field in skill import modal; logo-only Quartr icon
- Replace the hand-rolled drop zone in the skill import modal with the shared ChipModalField type='file' control (same component the Knowledge Base and Help & Support modals use), so the upload zone is visually consistent. - Migrate the GitHub-URL and paste-content rows to ChipModalField so every field shares the canonical px-4 gutter and error rendering, and align the 'or' dividers to match. - Drop the monospace font on the paste textarea so its text matches the rest of the modal. - Quartr icon now renders the logo mark only (no wordmark) as a black mark on a white rounded tile.
1 parent eb1009d commit e6ef14e

2 files changed

Lines changed: 61 additions & 152 deletions

File tree

apps/sim/app/workspace/[workspaceId]/skills/components/skill-import/skill-import.tsx

Lines changed: 44 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
'use client'
22

33
import type { ChangeEvent } from 'react'
4-
import { useCallback, useRef, useState } from 'react'
4+
import { useCallback, useState } from 'react'
55
import { getErrorMessage } from '@sim/utils/errors'
6-
import { Chip, ChipInput, ChipTextarea, Loader } from '@/components/emcn'
7-
import { Upload } from '@/components/emcn/icons'
6+
import { Chip, ChipInput, ChipModalField, ChipTextarea, Loader } from '@/components/emcn'
87
import { requestJson } from '@/lib/api/client/request'
98
import { importSkillContract } from '@/lib/api/contracts'
10-
import { cn } from '@/lib/core/utils/cn'
119
import {
1210
extractSkillFromZip,
1311
parseSkillMarkdown,
@@ -33,10 +31,6 @@ function isAcceptedFile(file: File): boolean {
3331
}
3432

3533
export function SkillImport({ onImport }: SkillImportProps) {
36-
const fileInputRef = useRef<HTMLInputElement>(null)
37-
38-
const [dragCounter, setDragCounter] = useState(0)
39-
const isDragging = dragCounter > 0
4034
const [fileState, setFileState] = useState<ImportState>('idle')
4135
const [fileError, setFileError] = useState('')
4236

@@ -84,39 +78,9 @@ export function SkillImport({ onImport }: SkillImportProps) {
8478
[onImport]
8579
)
8680

87-
const handleFileChange = useCallback(
88-
(e: ChangeEvent<HTMLInputElement>) => {
89-
const file = e.target.files?.[0]
90-
if (file) processFile(file)
91-
if (fileInputRef.current) fileInputRef.current.value = ''
92-
},
93-
[processFile]
94-
)
95-
96-
const handleDragEnter = useCallback((e: React.DragEvent) => {
97-
e.preventDefault()
98-
e.stopPropagation()
99-
setDragCounter((prev) => prev + 1)
100-
}, [])
101-
102-
const handleDragLeave = useCallback((e: React.DragEvent) => {
103-
e.preventDefault()
104-
e.stopPropagation()
105-
setDragCounter((prev) => prev - 1)
106-
}, [])
107-
108-
const handleDragOver = useCallback((e: React.DragEvent) => {
109-
e.preventDefault()
110-
e.stopPropagation()
111-
e.dataTransfer.dropEffect = 'copy'
112-
}, [])
113-
114-
const handleDrop = useCallback(
115-
(e: React.DragEvent) => {
116-
e.preventDefault()
117-
e.stopPropagation()
118-
setDragCounter(0)
119-
const file = e.dataTransfer.files?.[0]
81+
const handleFiles = useCallback(
82+
(files: File[]) => {
83+
const file = files[0]
12084
if (file) processFile(file)
12185
},
12286
[processFile]
@@ -159,55 +123,20 @@ export function SkillImport({ onImport }: SkillImportProps) {
159123

160124
return (
161125
<div className='flex flex-col gap-4'>
162-
{/* File drop zone */}
163-
<div className='flex flex-col gap-[9px]'>
164-
<span className='pl-0.5 font-normal text-[var(--text-muted)] text-sm'>Upload File</span>
165-
<button
166-
type='button'
167-
onClick={() => fileInputRef.current?.click()}
168-
onDragEnter={handleDragEnter}
169-
onDragOver={handleDragOver}
170-
onDragLeave={handleDragLeave}
171-
onDrop={handleDrop}
172-
disabled={fileState === 'loading'}
173-
className={cn(
174-
'flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-4 py-6 transition-colors',
175-
'border-[var(--border-1)] bg-[var(--surface-5)] hover-hover:bg-[var(--surface-active)] dark:bg-[var(--surface-4)]',
176-
isDragging && 'border-[var(--text-muted)] bg-[var(--surface-active)]',
177-
fileState === 'loading' && 'pointer-events-none opacity-60'
178-
)}
179-
>
180-
<input
181-
ref={fileInputRef}
182-
type='file'
183-
accept='.md,.zip'
184-
onChange={handleFileChange}
185-
className='hidden'
186-
/>
187-
{fileState === 'loading' ? (
188-
<Loader className='size-[16px] text-[var(--text-tertiary)]' animate />
189-
) : (
190-
<Upload className='size-[16px] text-[var(--text-tertiary)]' />
191-
)}
192-
<div className='flex flex-col gap-0.5 text-center'>
193-
<span className='text-[var(--text-primary)] text-sm'>
194-
{isDragging ? 'Drop file here' : 'Drop file here or click to browse'}
195-
</span>
196-
<span className='text-[11px] text-[var(--text-muted)]'>
197-
.md file with YAML frontmatter, or .zip containing a SKILL.md
198-
</span>
199-
</div>
200-
</button>
201-
{fileError && <p className='text-[12px] text-[var(--text-error)]'>{fileError}</p>}
202-
</div>
126+
<ChipModalField
127+
type='file'
128+
title='Upload File'
129+
accept='.md,.zip'
130+
onChange={handleFiles}
131+
disabled={fileState === 'loading'}
132+
label={fileState === 'loading' ? 'Importing…' : undefined}
133+
description='.md file with YAML frontmatter, or .zip containing a SKILL.md'
134+
error={fileError || undefined}
135+
/>
203136

204137
<ImportDivider />
205138

206-
{/* GitHub URL */}
207-
<div className='flex flex-col gap-[9px]'>
208-
<span className='pl-0.5 font-normal text-[var(--text-muted)] text-sm'>
209-
Import from GitHub
210-
</span>
139+
<ChipModalField type='custom' title='Import from GitHub' error={githubError || undefined}>
211140
<div className='flex gap-2'>
212141
<ChipInput
213142
placeholder='https://github.com/owner/repo/blob/main/SKILL.md'
@@ -217,7 +146,7 @@ export function SkillImport({ onImport }: SkillImportProps) {
217146
if (githubError) setGithubError('')
218147
}}
219148
disabled={githubState === 'loading'}
220-
className='flex-1'
149+
className='min-w-0 flex-1'
221150
/>
222151
<Chip
223152
flush
@@ -227,42 +156,43 @@ export function SkillImport({ onImport }: SkillImportProps) {
227156
{githubState === 'loading' ? <Loader className='size-[14px]' animate /> : 'Fetch'}
228157
</Chip>
229158
</div>
230-
{githubError && <p className='text-[12px] text-[var(--text-error)]'>{githubError}</p>}
231-
</div>
159+
</ChipModalField>
232160

233161
<ImportDivider />
234162

235-
{/* Paste content */}
236-
<div className='flex flex-col gap-[9px]'>
237-
<span className='pl-0.5 font-normal text-[var(--text-muted)] text-sm'>
238-
Paste SKILL.md Content
239-
</span>
240-
<ChipTextarea
241-
placeholder={
242-
'---\nname: my-skill\ndescription: What this skill does\n---\n\n# Instructions...'
243-
}
244-
value={pasteContent}
245-
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
246-
setPasteContent(e.target.value)
247-
if (pasteError) setPasteError('')
248-
}}
249-
resizable
250-
className='min-h-[120px] font-mono leading-relaxed'
251-
/>
252-
{pasteError && <p className='text-[12px] text-[var(--text-error)]'>{pasteError}</p>}
253-
<div className='flex justify-end'>
254-
<Chip variant='primary' flush onClick={handlePasteImport} disabled={!pasteContent.trim()}>
255-
Import
256-
</Chip>
163+
<ChipModalField type='custom' title='Paste SKILL.md Content' error={pasteError || undefined}>
164+
<div className='flex flex-col gap-[9px]'>
165+
<ChipTextarea
166+
placeholder={
167+
'---\nname: my-skill\ndescription: What this skill does\n---\n\n# Instructions...'
168+
}
169+
value={pasteContent}
170+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
171+
setPasteContent(e.target.value)
172+
if (pasteError) setPasteError('')
173+
}}
174+
resizable
175+
className='min-h-[120px]'
176+
/>
177+
<div className='flex justify-end'>
178+
<Chip
179+
variant='primary'
180+
flush
181+
onClick={handlePasteImport}
182+
disabled={!pasteContent.trim()}
183+
>
184+
Import
185+
</Chip>
186+
</div>
257187
</div>
258-
</div>
188+
</ChipModalField>
259189
</div>
260190
)
261191
}
262192

263193
function ImportDivider() {
264194
return (
265-
<div className='flex items-center gap-3 px-1'>
195+
<div className='flex items-center gap-3 px-2'>
266196
<div className='h-px flex-1 bg-[var(--border)]' />
267197
<span className='text-[11px] text-[var(--text-muted)]'>or</span>
268198
<div className='h-px flex-1 bg-[var(--border)]' />

apps/sim/components/icons.tsx

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3707,44 +3707,23 @@ export function QdrantIcon(props: SVGProps<SVGSVGElement>) {
37073707

37083708
export function QuartrIcon(props: SVGProps<SVGSVGElement>) {
37093709
return (
3710-
<svg {...props} viewBox='0 0 151 40' fill='none' xmlns='http://www.w3.org/2000/svg'>
3711-
<path
3712-
d='M11.5483 29.9995C11.5483 35.5222 19.8031 39.9995 26.1809 39.9995C29.574 39.9995 32.6254 38.7323 34.7384 36.7144L26.1809 29.9995H11.5483Z'
3713-
fill='currentColor'
3714-
/>
3715-
<path
3716-
d='M0 29.9996C0 35.5222 5.17047 39.9995 11.5483 39.9995V19.9998C5.17047 19.9998 0 24.4768 0 29.9996Z'
3717-
fill='currentColor'
3718-
/>
3719-
<path
3720-
d='M26.1808 9.99989V27.5075H37.7292V10.0249C37.7173 4.46985 32.552 -0.0135841 26.1808 2.98331e-05H11.4362V0.00114226C5.11012 0.0536532 0 4.50986 0 9.99989V19.9998H11.5483V9.99989H26.1808'
3721-
fill='currentColor'
3722-
/>
3723-
<path d='M28.1898 29.3275L40.7827 38.9295V29.3275H28.1898Z' fill='currentColor' />
3724-
<path
3725-
d='M88.7938 25.1089H88.7527C87.927 29.0309 85.4089 30.806 82.0238 30.806C77.0289 30.806 75.2949 27.5033 75.2949 23.3752V11.0324H78.8865V22.4671C78.8865 25.1089 80.0423 27.5033 83.5102 27.5033C86.8123 27.5033 88.7938 25.6872 88.7938 22.4671V11.0324H92.3854V30.3106H88.7938V25.1089Z'
3726-
fill='currentColor'
3727-
/>
3728-
<path
3729-
d='M107.351 23.1277V22.2193H101.613C99.1774 22.2193 97.8563 23.1277 97.8563 24.82C97.8563 26.3063 98.8062 27.7095 101.613 27.7095C105.163 27.7095 107.351 25.7697 107.351 23.1277ZM107.351 25.3982H107.31C106.443 28.948 103.842 30.7235 100.499 30.7235C96.7419 30.7235 94.3067 28.907 94.3067 25.2742C94.3067 21.8892 96.4529 19.371 101.118 19.371H107.351V18.3389C107.351 15.3254 106.196 13.6742 103.14 13.6742C100.375 13.6742 98.8473 14.9538 98.3108 18.1324L94.8843 17.6371C95.5447 13.1375 98.1869 10.6193 103.264 10.6193C108.631 10.6193 110.943 13.3438 110.943 18.2976V30.3106H107.351V25.3982'
3730-
fill='currentColor'
3731-
/>
3732-
<path
3733-
d='M112.909 30.3106V11.0324H116.5V16.9767H116.583C117.367 12.477 119.184 11.0324 122.28 11.0324H123.477V14.995H121.743C118.111 14.995 116.5 16.3988 116.5 20.1141V30.3106H112.909'
3734-
fill='currentColor'
3735-
/>
3736-
<path
3737-
d='M128.24 14.1166L125.391 15.0444V11.0205H128.24V5.69549H131.832V11.0205H137.982V15.0146L131.832 14.1166V24.0654C131.832 26.2122 132.657 27.3266 134.804 27.3266C135.836 27.3266 136.703 27.1203 137.735 26.8313L138.189 30.1339C136.868 30.5465 135.836 30.7944 134.06 30.7944C130.015 30.7944 128.24 28.1524 128.24 24.5609V14.1166'
3738-
fill='currentColor'
3739-
/>
3740-
<path
3741-
d='M140.129 30.3106V11.0324H143.72V16.9767H143.803C144.587 12.477 146.403 11.0324 149.499 11.0324H150.697V14.995H148.963C145.33 14.995 143.72 16.3988 143.72 20.1141V30.3106H140.129'
3742-
fill='currentColor'
3743-
/>
3744-
<path
3745-
d='M70.4206 19.1212C70.4206 14.0209 66.7718 10.045 61.8913 10.045C57.0108 10.045 53.0603 14.0209 53.0603 19.1212C53.0603 24.5028 56.9397 27.7175 61.8913 27.7175C66.8429 27.7175 70.4206 24.5028 70.4206 19.1212V19.1212ZM49.7827 19.1615C49.7827 11.9726 55.2655 6.67151 61.8913 6.67151C68.553 6.67151 73.6979 11.9726 73.6979 19.1615C73.6979 23.4184 71.6333 26.3923 68.9972 28.6411L72.0609 34.2237V34.304H68.4629L66.3614 30.2878C65.1145 30.7696 63.423 31.051 61.8913 31.051C55.1227 31.051 49.7827 26.3098 49.7827 19.1615'
3746-
fill='currentColor'
3747-
/>
3710+
<svg {...props} viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
3711+
<rect width='32' height='32' rx='6' fill='white' />
3712+
<g transform='translate(6 6.19) scale(0.16624)'>
3713+
<path
3714+
d='M34.0674 88.4993C34.0674 104.791 58.4191 117.999 77.2336 117.999C87.2434 117.999 96.2453 114.261 102.478 108.308L77.2336 88.4993H34.0674Z'
3715+
fill='black'
3716+
/>
3717+
<path
3718+
d='M0 88.4993C0 104.791 15.253 117.999 34.0677 117.999V58.9997C15.253 58.9997 0 72.2071 0 88.4993Z'
3719+
fill='black'
3720+
/>
3721+
<path
3722+
d='M77.2339 29.4999V81.1477H111.302V29.5736C111.267 13.1861 96.0292 -0.0400894 77.2339 7.20269e-05H33.737V0.00335371C15.075 0.158262 0 13.3042 0 29.4999V58.9997H34.0677V29.4999H77.2339'
3723+
fill='black'
3724+
/>
3725+
<path d='M83.1602 86.5171L120.31 114.843V86.5171H83.1602Z' fill='black' />
3726+
</g>
37483727
</svg>
37493728
)
37503729
}

0 commit comments

Comments
 (0)