Skip to content

Commit cd2d467

Browse files
committed
feat(emcn): view-only field primitives + scheduled-task modals
- ChipCopyInput (canonical view-only copy field), ChipTimePicker, ChipModalField type='copy', ChipTextarea viewOnly; new border chip variant and shared chipPrimaryFillTokens - migrate ~40 consumers off disabled inputs and the deleted CopyableValueField; ChipConfirmModal description->text and secondaryActions[] API sweep - scheduled-tasks: rename create-task-modal to task-modal, add task-details-modal + task-context-menu, useScheduledTasks hook - home: extract prompt-editor (usePromptEditor) out of user-input
1 parent 2e52a5d commit cd2d467

81 files changed

Lines changed: 3291 additions & 2109 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/rules/emcn-components.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items
2222

2323
- **`Chip` / `ChipLink`** — the pill button (`<button>` / Next `<Link>`). Variants: `ghost`, `filled`, `primary`, `destructive`, `border-shadow`. `leftIcon`/`rightIcon`, `active`, `fullWidth`, `flush`.
2424
- **`ChipInput`** — single-line text field. `icon`, `endAdornment`, `error`, `inputClassName` (inner `<input>`); `className` styles the chrome wrapper.
25-
- **`ChipTextarea`** — multi-line sibling. `error`, `resizable` (off by default).
25+
- **`ChipCopyInput`** — the canonical view-only field: a read-only `ChipInput` at full opacity with a trailing copy-to-clipboard button. View-only is a display mode, not a disabled state — reach for it (or `ChipModalField type='copy'`) over a `disabled` (greyed) input for values the user cannot edit.
26+
- **`ChipTextarea`** — multi-line sibling. `error`, `resizable` (off by default), `viewOnly` (read-only at full opacity with the default cursor — the multi-line counterpart of `ChipCopyInput`).
2627
- **`ChipDropdown`** — pill that opens a menu. Single OR multi-select via the discriminated `multiple` prop (one component, not two). Owns its trailing chevron — no `rightIcon`.
2728
- **`ChipSelect` / `ChipCombobox`**`Combobox`-backed pickers with search, groups, multi-select; for richer lists than `ChipDropdown`.
28-
- **`ChipModal` + `ChipModalField`** — declarative compact modal. The field's `type` (`input` | `email` | `textarea` | `dropdown` | `file` | `emails` | `custom`) picks the control and **owns all chrome** — consumers describe intent, never pass `variant`/`className`/`id` to the inner control. `custom` is the escape hatch. **Every body field MUST be a `ChipModalField`** — never hand-roll a field row (raw `<div>` + hand-rolled `<p>`/`<label>` title + bare `ChipInput`/`ChipTextarea`). `ChipModalBody` applies `px-2` + `gap-4`; `ChipModalField` adds another `px-2`, so each field lands at effective `px-4`, exactly matching the `px-4` header/footer — a hand-rolled row skips that gutter and sits misaligned at `px-2`. For controls the field doesn't cover (`ChipCombobox`, `ChipSelect`, `DatePicker`, `TimePicker`, `ButtonGroup`, arbitrary JSX), use `type='custom'` with a `title` — it still applies the gutter and renders the canonical `Label`.
29+
- **`ChipModal` + `ChipModalField`** — declarative compact modal. The field's `type` (`input` | `email` | `textarea` | `dropdown` | `copy` | `file` | `emails` | `custom`) picks the control and **owns all chrome** — consumers describe intent, never pass `variant`/`className`/`id` to the inner control. `custom` is the escape hatch. **Every body field MUST be a `ChipModalField`** — never hand-roll a field row (raw `<div>` + hand-rolled `<p>`/`<label>` title + bare `ChipInput`/`ChipTextarea`). `ChipModalBody` applies `px-2` + `gap-4`; `ChipModalField` adds another `px-2`, so each field lands at effective `px-4`, exactly matching the `px-4` header/footer — a hand-rolled row skips that gutter and sits misaligned at `px-2`. For controls the field doesn't cover (`ChipCombobox`, `ChipSelect`, `DatePicker`, `TimePicker`, `ButtonGroup`, arbitrary JSX), use `type='custom'` with a `title` — it still applies the gutter and renders the canonical `Label`.
2930
- **`ChipSwitch`** — segmented pill control (built from `chipVariants`).
3031
- **`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
3132
- **`ChipDatePicker`** — chip-styled date field.
33+
- **`ChipTimePicker`** — minute-granular time sibling of `ChipDatePicker`, a `ChipInput` that leniently parses typed input (`9:47`, `947`, `2:05pm`, `14:30`), commits on Enter/blur, and re-renders the canonical `9:47 AM` label.
3234
- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
3335

3436
## Authoring principles

apps/sim/app/workspace/[workspaceId]/components/credential-detail/components/copyable-value-field.tsx

Lines changed: 0 additions & 48 deletions
This file was deleted.

apps/sim/app/workspace/[workspaceId]/components/credential-detail/components/unsaved-changes-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function UnsavedChangesModal({ open, onOpenChange, onDiscard }: UnsavedCh
1919
onOpenChange={onOpenChange}
2020
srTitle='Unsaved Changes'
2121
title='Unsaved Changes'
22-
description='You have unsaved changes. Are you sure you want to discard them?'
22+
text='You have unsaved changes. Are you sure you want to discard them?'
2323
dismissLabel='Keep editing'
2424
confirm={{ label: 'Discard Changes', onClick: onDiscard }}
2525
/>

apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export { AddPeopleModal } from './components/add-people-modal'
22
export { CHIP_FIELD_INPUT, CHIP_FIELD_SHELL } from './components/chip-field'
3-
export { CopyableValueField } from './components/copyable-value-field'
43
export { CredentialDetailHeading } from './components/credential-detail-heading'
54
export { CredentialDetailLayout } from './components/credential-detail-layout'
65
export { CredentialMembersSection } from './components/credential-members-section'

apps/sim/app/workspace/[workspaceId]/files/components/delete-confirm-modal/delete-confirm-modal.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,13 @@ export const DeleteConfirmModal = memo(function DeleteConfirmModal({
3939
onOpenChange={onOpenChange}
4040
srTitle={title}
4141
title={title}
42-
description={
43-
<>
44-
Are you sure you want to delete{' '}
45-
{fileName ? (
46-
<span className='font-medium text-[var(--text-primary)]'>{fileName}</span>
47-
) : (
48-
`${totalCount} item${totalCount === 1 ? '' : 's'}`
49-
)}
50-
? {consequence}
51-
</>
52-
}
42+
text={[
43+
'Are you sure you want to delete ',
44+
fileName
45+
? { text: fileName, bold: true }
46+
: `${totalCount} item${totalCount === 1 ? '' : 's'}`,
47+
`? ${consequence}`,
48+
]}
5349
confirm={{
5450
label: 'Delete',
5551
onClick: onDelete,

apps/sim/app/workspace/[workspaceId]/files/files.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1847,7 +1847,7 @@ export function Files() {
18471847
onOpenChange={setShowUnsavedChangesAlert}
18481848
srTitle='Unsaved Changes'
18491849
title='Unsaved Changes'
1850-
description='You have unsaved changes. Are you sure you want to discard them?'
1850+
text='You have unsaved changes. Are you sure you want to discard them?'
18511851
dismissLabel='Keep editing'
18521852
confirm={{ label: 'Discard Changes', onClick: handleDiscardChanges }}
18531853
/>

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export type WindowWithSpeech = Window & {
3737
}
3838

3939
export interface PlusMenuHandle {
40-
open: (anchor?: { left: number; top: number }, options?: { mention?: boolean }) => void
40+
/** Opens the menu anchored at a viewport position (caret or trigger rect). */
41+
open: (anchor: { left: number; top: number }, options?: { mention?: boolean }) => void
4142
close: () => void
4243
moveActive: (delta: number) => void
4344
selectActive: () => boolean
@@ -46,23 +47,27 @@ export interface PlusMenuHandle {
4647
/**
4748
* Box and typography shared by the textarea and its mirror overlay — both must
4849
* produce identical line wrapping so the overlay text sits exactly over the
49-
* (transparent) textarea text.
50+
* (transparent) textarea text. The scale is the canonical chip text-field
51+
* scale ({@link ChipTextarea}: `text-sm`, default tracking), so the editor
52+
* reads identically in the chat input and inside chip modals — one size,
53+
* everywhere.
5054
*/
5155
const FIELD_MIRROR_CLASSES = cn(
52-
'm-0 box-border min-h-[24px] w-full break-words [overflow-wrap:anywhere] border-0 bg-transparent',
53-
'px-1 py-1 font-body text-[15px] leading-[24px] tracking-[-0.015em]'
56+
'm-0 box-border min-h-[20px] w-full break-words [overflow-wrap:anywhere] border-0 bg-transparent',
57+
'px-1 py-1 font-body text-sm leading-[20px]'
5458
)
5559

5660
/**
5761
* The textarea grows to its full content height (`h-auto`, no internal scroll);
5862
* the shared scroller clips and scrolls it. Its text is transparent so the
59-
* mirror overlay shows through; only the caret paints.
63+
* mirror overlay shows through; only the caret paints. The placeholder uses
64+
* the canonical `--text-muted`, matching every other chip text field.
6065
*/
6166
export const TEXTAREA_BASE_CLASSES = cn(
6267
FIELD_MIRROR_CLASSES,
6368
'block h-auto resize-none overflow-hidden',
6469
'text-transparent caret-[var(--text-primary)] outline-none',
65-
'placeholder:font-[380] placeholder:text-[var(--text-subtle)]',
70+
'placeholder:text-[var(--text-muted)]',
6671
'focus-visible:ring-0 focus-visible:ring-offset-0'
6772
)
6873

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ export { DropOverlay } from './drop-overlay'
2525
export { MicButton } from './mic-button'
2626
export type { AvailableResourceGroup } from './plus-menu-dropdown'
2727
export { PlusMenuDropdown } from './plus-menu-dropdown'
28+
export type {
29+
PromptEditorInstance,
30+
PromptEditorKeyPolicy,
31+
PromptEditorProps,
32+
UsePromptEditorProps,
33+
} from './prompt-editor'
34+
export { PromptEditor, usePromptEditor } from './prompt-editor'
2835
export { SendButton } from './send-button'
2936
export type { SkillsMenuHandle } from './skills-menu-dropdown/skills-menu-dropdown'
3037
export { SkillsMenuDropdown } from './skills-menu-dropdown/skills-menu-dropdown'

0 commit comments

Comments
 (0)