The full calendar UI used to pick a date — header, weekday row, and a 7×6 day grid.
DatePicker mapping.Contexts are illustrative. Final screens will reference actual GCash patterns.
isDisabled uses Yes/No (should be true/false). Axis design is ambiguous — Disabled is conceptually a state, not orthogonal to State, yielding 5 usable of 8 combinations. Diverges from Input Field / Select Field pattern which use a single State enum including Disabled.DatePicker / Compose DatePickerDialog.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default (empty) | Yes | Yes | State=Default, isFilled=false | Gray #D7E0EF border. Placeholder "Value" #90A8D0. |
| Default (filled) | Yes | Yes | State=Default, isFilled=true | Gray border, selected date shown in #0A2757. |
| Active (opening — empty) | Yes | Yes | State=Active, isFilled=false | Blue #005CE5 2px border. Inline calendar panel attached below. |
| Active (filled) | Yes | Yes | State=Active, isFilled=true | Blue border, filled value, calendar visible. |
| Disabled | Yes | Yes | isDisabled=Yes | Gray #EEF2F9 bg. Value #90A8D0. No border. Calendar glyph dims. |
| Error | No | No | — | Not defined. Sibling Select/Input Field both support Error — must be added for validation flows (e.g. "Enter a valid birth date"). |
| Pressed | No | No | — | Not defined. Touch feedback expected on both platforms. |
- Layer naming inconsistent with sibling fields. Trigger frame is named
Select Fieldand its innercontainer/text-containerare generic — but the component itself isDate Picker. The Date Picker - Group popover has layer names likerow,Monday...Saturday(day-of-week labels as layer names rather than semantic roles). Normalize to kebab-case semantic names. C1 · Layer Structure & Naming -
isDisabledusesYes/No,isFilledusesFalse/True. Should be lowercasetrue/falsefor direct SwiftBool/ KotlinBooleanmapping. Inconsistent casing between the two booleans also breaks Code Connect naming uniformity. C2 · Variant & Property Naming - Axis design produces invalid combinations.
State × isFilled × isDisabledis a 2×2×2=8 matrix but only 5 combinations exist because Disabled collapses State. Siblings (Input Field, Select Field) use a singleStateenum includingDisabled— Date Picker should follow that pattern. C2 · Variant & Property Naming - Calendar popover is composed inline, not as an overlay. Date Picker - Group is nested inside the trigger's stack and positioned absolutely — it cannot be swapped for a sheet or presented by the native date dialog. SwiftUI
DatePickerand ComposeDatePickerDialogboth expect the trigger and the picker to be separable. C4 · Native Mappability - No Error state. Input Field, Select Field, and Dropdown all support Error; Date Picker does not. Birth-date and expiry flows routinely need inline validation ("Must be 18+", "Date can't be in the past"). C5 · Interaction State Coverage
- No Pressed state. Standard tap feedback (iOS highlight, Android ripple) is missing. Consumers improvise it per-screen. C5 · Interaction State Coverage
- Calendar glyph is a raster
shape_fullimage. The trigger icon pulls from an MCP asset URL rather than a vector icon instance. Won't scale cleanly, won't inherit state tokens, can't be swapped. C6 · Asset & Icon Quality - Code Connect mappings not registered. Blocked by C1/C2/C4/C5/C6 above. C7 · Code Connect Linkability
- Family — Unify the two cell primitives.
Date Picker - Item(day cells) andMonth and Year Picker - Item(month/year cells) are both selectable cells with identical state semantics (Default/Today/Selected/Disabled). Only size + typography differ. Propose onePicker Cellcomponent with axeskind: day | month | year+state: default | today | selected | disabled. Reduces 10 variants across 2 components to 1 component with 2 clean axes. Family - Composition — Wrap the native date pickers instead of redrawing them. iOS:
DatePicker(selection:, displayedComponents:)with.datePickerStyle(.graphical|.compact|.wheel|.field). Android: Material 3DatePicker+DatePickerDialog. The DSEBDatePickershould be a thin wrapper that tokenizes the trigger and cells to match brand — not a from-scratch calendar redraw in Figma. Keeps accessibility, localization, and leap-year logic native. Composition - Field-trigger consistency — Unify with Input Field as
type: .date. Structurally this is an Input Field with a date value display + calendar glyph. Matches SwiftUITextField(value:, format: .date)/ ComposeOutlinedTextField(readOnly=true) + DatePickerDialog. Consolidates a fragmented field family and inherits Input Field's Error state, label slot, and helper text. Family - Collapse
State × isFilled × isDisabledinto a singlestateenum. Target schema:state: default | active | error | disabled+isFilled: true | false. Matches Input Field (8 variants from 4×2). Removes the 3 invalid combinations in the current matrix and aligns with the rest of the Form Elements family. Property - Separate the calendar panel from the trigger. Move Date Picker - Group to a true overlay component (top-anchored, dismissible, shadow-elevated) rather than an inline child of the trigger. Lets the trigger be used alone (e.g. inline text input mode) and lets the panel be presented by either a dropdown or a sheet depending on context. Composition
- Add Error and Pressed states. Error: red border + subtext (mirror Input Field tokens). Pressed: slight bg tint for tap feedback. Required for form accessibility and parity with the rest of Form Elements. State
- Replace the calendar raster with a vector icon instance. Use the existing DS calendar icon (if present) or add one to the icon library. Color-bind to
selected-field/color/{state}/iconso it dims with Disabled automatically. Asset - Rename booleans to lowercase
true/false.isDisabled=Yes/NotoisDisabled=true/false;isFilled=False/TruetoisFilled=false/true. Consistent with the naming convention adopted by Input Field. Rename
Calendar trigger field. Flip the State, Filled, and Disabled controls to walk through every variant.
Trigger colors reuse the selected-field token family (shared with Dropdown and Select Field). Calendar panel uses dedicated date-picker tokens.
| Role | Token | DEFAULT | ACTIVE | DISABLED |
|---|---|---|---|---|
| Trigger border | selected-field/color/{state}/border | #D7E0EF | #005CE5 (2px) | hidden |
| Trigger bg | selected-field/color/{state}/bg | #FFFFFF | #FFFFFF | #EEF2F9 |
| Value (filled) | selected-field/color/{state}/value | #0A2757 | #0A2757 | #90A8D0 |
| Placeholder | selected-field/color/{state}/placeholder | #90A8D0 | #90A8D0 | – |
| Calendar icon | selected-field/color/{state}/icon | #005CE5 | #005CE5 | #9BC5FD |
| Header label | formgroup-header/color/label | #0A2757 | #0A2757 | #0A2757 |
| Panel bg | date-picker/month-header/color/bg | – | #FFFFFF | – |
| Panel border | date-picker/month-header/color/border | – | #E5EBF4 | – |
| Panel shadow | elevation/app/shadow | – | 0 6px 12px rgba(2,14,34,.16) | – |
| Month label | date-picker/month-header/color/label | – | #0A2757 | – |
| Month chevron | date-picker/month-header/color/icon | – | #005CE5 | – |
| Weekday label | date-picker/week-header/color/label | – | #0A2757 | – |
| Day (unselected) | date-picker/day/color/unselected/label | – | #0A2757 | – |
| Day (today border) | border/color-border-primary | – | #005CE5 (1.5px) | – |
| Day (prev/next month) | text/color-text-disabled | – | #C2CFE5 | – |
iOS — Swift Package Manager
// In Xcode: File → Add Package Dependencies "https://github.com/AY-Org/eb-ds-ios"
Android — Gradle (Kotlin DSL)
dependencies { implementation("com.eastblue.ds:date-picker:1.0.0") }
Import
import EastBlueDS // SwiftUI import com.eastblue.ds.datepicker.* // Compose
Package not yet published. These are the planned distribution paths. The recommendation is to wrap native date pickers rather than redraw them.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| State = Default | — | — |
| State = Active | isPresented: Binding<Bool> | showPicker: Boolean |
| isFilled (False/True) | selection: Binding<Date?> | selectedDate: LocalDate? |
| isDisabled (Yes/No) | .disabled(true) | enabled = false |
| label (formgroup-header) | label: String | label: String |
| subtext (optional) | helperText: String? | helperText: String? |
EBDatePicker("Date of Birth", selection: $birthDate)
EBDatePicker( label = "Date of Birth", selectedDate = birthDate, onDateSelected = { birthDate = it } )
EBDatePicker("Date of Birth", selection: $birthDate) .ebHelperText("You must be 18 or older")
EBDatePicker( label = "Date of Birth", selectedDate = birthDate, onDateSelected = { birthDate = it }, helperText = "You must be 18 or older" )
EBDatePicker("Date of Birth", selection: $birthDate) .disabled(true)
EBDatePicker( label = "Date of Birth", selectedDate = birthDate, onDateSelected = { birthDate = it }, enabled = false )
| Requirement | iOS | Android |
|---|---|---|
| Minimum touch target | 44 × 44 pt | 48 × 48 dp |
| Accessibility label | .accessibilityLabel("Date of Birth") | contentDescription = "Date of Birth" |
| Selected value | .accessibilityValue(formattedDate) | semantics { stateDescription } |
| Role | VoiceOver announces as date picker | TalkBack announces via Role.Button + expanded state |
| Localization | Native DatePicker respects locale calendar | Native DatePickerDialog respects locale calendar |
Do
Wrap the native DatePicker / DatePickerDialog so accessibility, localization, and leap-year logic are handled by the OS.
Don't
Redraw the calendar grid from scratch — the Figma calendar is a visual spec, not a reimplementation target.
Do
Pair the trigger with a visible label above it. Use helper text to clarify expected range ("Pick a future date").
Don't
Use the trigger as a free-text date field — it's for picking from the panel, not typing. If free-entry is required, use Input Field with a date format.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Needs Refinement | Trigger reuses Select Field / container / text-container. Day cell rows use weekday names (Monday…Saturday) as layer names. |
| C2 | Variant & Property Naming | Requires Rework | isDisabled=Yes/No, isFilled=False/True. Inconsistent casing. Axis design yields 5 of 8 combinations. |
| C3 | Token Coverage | Ready | All colors bound to selected-field and date-picker tokens. Spacing and radius tokens consistent. |
| C4 | Native Mappability | Requires Rework | Inline calendar panel blocks mapping to native DatePicker / DatePickerDialog. Must be separable. |
| C5 | Interaction State Coverage | Requires Rework | No Error state (siblings have it). No Pressed state. |
| C6 | Asset & Icon Quality | Requires Rework | Calendar glyph is a raster shape_full image. Month chevrons use shape_full images as well. |
| C7 | Code Connect Linkability | Needs Refinement | Blocked by C1/C2/C4/C5/C6. |
| Aspect | Status | Notes |
|---|---|---|
| Property naming | Requires Rework | isDisabled/isFilled need lowercase boolean values |
| Axis design | Requires Rework | Collapse to single state enum matching Input/Select Field pattern |
| Composition | Requires Rework | Calendar panel must be separable to map native dialog/sheet |
| Asset quality | Requires Rework | Calendar glyph + chevrons must be vector icons |
| State coverage | Requires Rework | Error and Pressed states missing |
| Native component file | Needs Refinement | EBDatePicker.swift / EBDatePicker.kt not yet created |
Declared as a 2×2×2 matrix (State × isFilled × isDisabled) but only 5 combinations exist — Disabled collapses both State and isFilled=false into a single variant.
| State | isFilled | isDisabled | Node ID |
|---|---|---|---|
| Default | false | No | 12879:49784 |
| Default | true | No | 12890:42872 |
| Active | false | No | 12879:49827 |
| Active | true | No | 13342:9932 |
| Default | true | Yes | 13342:10148 |
State × isFilled × isDisabled. Field-shaped trigger with inline calendar panel on Active. Lead component of the Date Picker family.
Documentedstate enum like Input/Select Field.
OpenisDisabled=Yes/No and isFilled=False/True. Both need lowercase true/false for Code Connect.
OpenDatePicker / DatePickerDialog.
Openshape_full image reference, not a vector icon instance. Month chevrons also use raster shape_full assets.
Open