# @triptyk/ember-input

A collection of composable Ember input components for building forms.

## Installation

```bash
ember install @triptyk/ember-input
```

Install peer dependencies if needed:

```bash
ember install ember-flatpickr
```

## Importing Types

Add the Glint template-registry to your global.d.ts file:

```ts
import '@glint/environment-ember-loose';
import type EmberInputRegistry from '@triptyk/ember-input/template-registry';

declare module '@glint/environment-ember-loose/registry' {
  export default interface Registry
    extends EmberInputRegistry {}
}
```

## HTML Naming Conventions

Every component has an HTML element with a class assigned to it. The naming follows this rule:

**Path of the component to kebab case**

Examples:
- `<TpkInput>`: .tpk-input
- `<TpkInput::Label>`: .tpk-input-label
- `<TpkInput::Input>`: .tpk-input-input

Use the `@classless` argument to prevent the base class from being applied.

## Components

### TpkInput

A composable HTML input element.

**Usage:**
```hbs
<TpkInput 
  @changeEvent='input' 
  @value={{this.value}} 
  @onChange={{this.onChange}} 
  @label="Name" 
as |I|>
  <I.Label />
  <I.Input />
</TpkInput>
```

**Arguments:**
- `@value`: String - The input value
- `@onChange`: Function - Callback when value changes (receives value and event)
- `@changeEvent`: 'input' | 'change' - Which event triggers onChange
- `@label`: String - Label text
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `I.Label`: Label component
- `I.Input`: Input component

### TpkButton

A button component that performs an onClick task with spam prevention.

**Usage:**
```hbs
<TpkButton
  @onClick={{this.incrementCounter}}
  @allowSpam={{true}}
  class="button"
>
  Click me
</TpkButton>
```

**Arguments:**
- `@onClick`: Function - Task to execute on click
- `@allowSpam`: Boolean - If false (default), prevents spam clicks by waiting for current task to finish

### TpkCheckbox

A composable checkbox input element.

**Usage:**
```hbs
<TpkCheckbox
  @checked={{this.checked}}
  @onChange={{this.onChange}}
  @label="Checkbox"
as |C|>
  <C.Label />
  <C.Input />
</TpkCheckbox>
```

**Arguments:**
- `@checked`: Boolean - Checked state
- `@onChange`: Function - Callback when value changes
- `@label`: String - Label text
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `C.Label`: Label component
- `C.Input`: Checkbox input component

**CSS Classes:**
- `.tpk-checkbox-input`: Main checkbox container
- `.tpk-checkbox-label`: Label styling

### TpkSelect

A select component with custom styling and options.

**Usage:**
```hbs
<TpkSelect
  @options={{this.sauceOptions}}
  @selected={{this.selection}}
  @onChange={{this.onChange}}
  @label="Select your favorite sauce:"
as |S|>
  <S.Label />
  <S.Button>
    {{if S.hasSelection S.selected "..."}}
  </S.Button>
  <S.Options as |Opts|>
    <Opts as |opt|>
      {{opt.option}}
    </Opts>
  </S.Options>
</TpkSelect>
```

**Arguments:**
- `@options`: Array - Array of options
- `@selected`: Any - Currently selected value(s)
- `@onChange`: Function - Callback when selection changes
- `@label`: String - Label text
- `@multiple`: Boolean - Allow multiple selections (stores in array)
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `S.Label`: Label component
- `S.Button`: Button that opens the dropdown
- `S.Options`: Options list
- `S.hasSelection`: Boolean indicating if there's a selection
- `S.selected`: Current selection value

### TpkRadio

A composable radio input element.

**Usage:**
```hbs
<TpkRadio 
  @label='Female'
  @selected="Female"
  @value="Female"
  @name="gender-radio"
  @onChange={{this.setRadio}}
as |C|>
  <C.Input />
  <C.Label />
</TpkRadio>
```

**Arguments:**
- `@label`: String - Label text
- `@selected`: String - Which value is selected (set on at least one radio in group)
- `@value`: String - Value of this radio option
- `@name`: String - Group name (must be identical for all radios in group)
- `@onChange`: Function - Callback when selection changes
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `C.Input`: Radio input component
- `C.Label`: Label component

### TpkFile

A file input component for handling file uploads.

**Usage:**
```hbs
<TpkFile 
  @label="Add your profile image"
  @accept="image/*"
  @disabled={{false}}
  @changeEvent="change"
  @onChange={{this.onChange}}
as |I|>
  <I.Label />
  <I.Input />
</TpkFile>
```

**Arguments:**
- `@accept`: String - File types to accept (e.g., "image/*")
- `@disabled`: Boolean - Disable the input
- `@multiple`: Boolean - Allow multiple file selection
- `@changeEvent`: 'input' | 'change' - Which event triggers onChange (required)
- `@onChange`: Function (Event) => void - Callback when files change (required)
- `@label`: String - Label text
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `I.Label`: Label component
- `I.Input`: File input component

### TpkTextarea

A composable textarea input element with character counting.

**Usage:**
```hbs
<TpkTextarea
  @value={{this.value}}
  @onChange={{this.onChange}}
  @maxLength={{150}}
  @changeEvent='change'
  @label="Description"
as |C|>
  <div>
    <C.Label />
    <span class="text-area-count">
      {{C.charCount}} / {{C.maxLength}}
    </span>
  </div>
  <C.Input />
</TpkTextarea>
```

**Arguments:**
- `@value`: String - Textarea content
- `@onChange`: Function - Callback when value changes (receives value and event)
- `@maxLength`: Number - Maximum character length
- `@changeEvent`: 'input' | 'change' - Which event triggers onChange
- `@label`: String - Label text
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `C.Label`: Label component
- `C.Input`: Textarea component
- `C.charCount`: Current character count
- `C.maxLength`: Maximum character count

**CSS Classes:**
- `.tpk-textarea`: Main container
- `.tpk-label`: Label styling
- `.tpk-textarea-input`: Textarea styling

### TpkDatepicker

A date picker component with extensive customization options using flatpickr.

**Usage:**
```hbs
<TpkDatepicker
  @label="Date picker"
  @value={{this.selectedDate}}
  @onChange={{this.onChange}}
as |D|>
  <D.Label />
  <D.Input />
</TpkDatepicker>
```

**Mandatory Arguments:**
- `@value`: Date | string | undefined - Initial date value
- `@onChange`: Function (Date[]) => void - Callback when date changes

**Optional Arguments:**
- `@disabled`: Boolean - Disable the picker
- `@mask`: String - Input mask using IMask (e.g., 'd-m/Y')
- `@placeholder`: String - Placeholder text
- `@onClose`: Function - Callback when picker closes
- `@label`: String - Label text
- `@stepping`: Number - Minutes to step in time picker (default: 5)
- `@mode`: 'multiple' | 'range' - Selection mode
- `@multipleDatesSeparator`: String - Separator for multiple dates
- `@range`: Boolean - Enable range selection (default: false)
- `@useCurrent`: Boolean - Use current date/time as initial (default: false)
- `@promptTimeOnDateChange`: Boolean - Show time picker after date selection
- `@todayButton`: Boolean - Show "Today" button
- `@clearButton`: Boolean - Show "Clear" button (default: true)
- `@closeButton`: Boolean - Show "Close" button (default: true)
- `@enableTime`: Boolean - Enable time selection (default: false)
- `@noCalendar`: Boolean - Disable calendar (default: true)
- `@enableSecond`: Boolean - Enable seconds in time picker
- `@keepOpen`: Boolean - Keep picker open after selection
- `@locale`: String - Locale for labels (default: 'fr')
- `@dateFormat`: String - Display format (default: 'dd/MM/yyyy')
- `@minDate`: Date - Minimum selectable date
- `@maxDate`: Date - Maximum selectable date
- `@daysOfWeekDisabled`: Array - Days to disable (0-6)
- `@disabledTimeIntervals`: Array - Time intervals to disable
- `@disabledDates`: Array - Specific dates to disable
- `@enabledDates`: Array - Only these dates enabled
- `@disabledHours`: Array - Hours to disable
- `@enabledHours`: Array - Only these hours enabled
- `@viewMode`: 'clock' | 'calendar' | 'months' | 'years' | 'decades' - Initial view
- `@classless`: Boolean - Override default CSS classes

**Yields:**
- `D.Label`: Label component
- `D.Input`: Input component

**Limitations:**
- Cannot have default value for 'range' and 'multiple' modes

**Examples:**

Range picker:
```hbs
<TpkDatepicker
  @value={{this.selectedDates}}
  @onChange={{this.onChangeRange}}
  @range={{true}}
  @multipleDatesSeparator=" jusqu'au "
as |D|>
  <D.Label />
  <D.Input />
</TpkDatepicker>
```

Time picker:
```hbs
<TpkDatepicker
  @value={{this.selectedDate}}
  @onChange={{this.onChange}}
  @enableTime={{true}}
  @noCalendar={{true}}
  @dateFormat="HH:mm"
  @mask="H:M"
as |D|>
  <D.Label />
  <D.Input />
</TpkDatepicker>
```

## Compatibility

- Ember.js v4.8 or above
- Embroider or ember-auto-import v2

## License

MIT License
