diff --git a/packages/oui-angular/src/index.js b/packages/oui-angular/src/index.js
index 04abcbef..d90dbe87 100644
--- a/packages/oui-angular/src/index.js
+++ b/packages/oui-angular/src/index.js
@@ -39,6 +39,7 @@ import Switch from "@ovh-ui/oui-switch";
import Tabs from "@ovh-ui/oui-tabs";
import Textarea from "@ovh-ui/oui-textarea";
import Tile from "@ovh-ui/oui-tile";
+import Timepicker from "@ovh-ui/oui-timepicker";
import Tooltip from "@ovh-ui/oui-tooltip";
export default angular
@@ -84,6 +85,7 @@ export default angular
Tabs,
Textarea,
Tile,
+ Timepicker,
Tooltip
])
.name;
diff --git a/packages/oui-calendar/README.md b/packages/oui-calendar/README.md
index 0d54e3cd..29e3a1c2 100644
--- a/packages/oui-calendar/README.md
+++ b/packages/oui-calendar/README.md
@@ -160,6 +160,16 @@ Use `mode` to set a different selection mode for the calendar
* `selectedDates` returns an array of Date objects selected by the user. When there are no dates selected, the array is empty.
* `dateStr` returns a string representation of the latest selected Date object by the user. The string is formatted as per the `dateFormat` option.
+## Variants
+
+### Timepicker
+
+See Action menu component.
+
+```html:preview
+
+```
+
## API
| Attribute | Type | Binding | One-time Binding | Values | Default | Description
@@ -168,19 +178,19 @@ Use `mode` to set a different selection mode for the calendar
| `id` | string | @? | yes | n/a | n/a | id attribute of the field
| `name` | string | @? | yes | n/a | n/a | name attribute of the field
| `placeholder` | string | @? | yes | n/a | n/a | placeholder text
-| `inline` | boolean | | no | `true`, `false` | `false` | show the calendar below the input
-| `static` | boolean | | no | `true`, `false` | `false` | position the calendar relatively to the input
| `mode` | string | @? | yes | `single`, `multiple`, `range` | `single` | selection mode
| `format` | string | @? | yes | See [Formatting Tokens](https://flatpickr.js.org/formatting/) | `Y-m-d` | format the date of the model
| `alt-format` | string | @? | yes | See [Formatting Tokens](https://flatpickr.js.org/formatting/) | `Y-m-d` | format the date of the field. `format` is used if undefined
| `append-to-body` | boolean | | yes | `true`, `false` | `false` | append the calendar to the body of the page
-| `inline` | boolean | | yes | `true`, `false` | `false` | show the calendar below the input in `inline-block`
+| `inline` | boolean | | no | `true`, `false` | `false` | show the calendar below the input
+| `static` | boolean | | no | `true`, `false` | `false` | position the calendar relatively to the input
| `max-date` | object | | yes | See [Supplying Dates](https://flatpickr.js.org/examples/#supplying-dates-for-flatpickr) | n/a | specifies the maximum/latest date (inclusively) allowed for selection
| `min-date` | object | | yes | See [Supplying Dates](https://flatpickr.js.org/examples/#supplying-dates-for-flatpickr) | n/a | specifies the minimum/earliest date (inclusively) allowed for selection
| `disable-date` | array | | yes | See [Supplying Dates](https://flatpickr.js.org/examples/#supplying-dates-for-flatpickr) | n/a | make certain dates unavailable for selection
| `enable-date` | array | | yes | See [Supplying Dates](https://flatpickr.js.org/examples/#supplying-dates-for-flatpickr) | n/a | make certain dates only available for selection
| `enable-time` | boolean | | yes | `true`, `false` | `false` | enables time selection
| `week-numbers` | boolean | | yes | `true`, `false` | `false` | week numbers flag
+| `options` | object | | yes | See [Options](https://flatpickr.js.org/options/) | n/a | flatpickr options for more advanced configuration
| `disabled` | boolean | | no | `true`, `false` | `false` | disabled flag
| `required` | boolean | | no | `true`, `false` | `false` | required flag
| `on-change` | function | & | no | n/a | n/a | handler triggered when the user selects a date, or changes the time on a selected date
diff --git a/packages/oui-calendar/src/calendar.component.js b/packages/oui-calendar/src/calendar.component.js
index 3b3c039c..96cd38e0 100644
--- a/packages/oui-calendar/src/calendar.component.js
+++ b/packages/oui-calendar/src/calendar.component.js
@@ -14,16 +14,18 @@ export default {
appendToBody: "",
inline: "",
+ "static": "",
maxDate: "",
minDate: "",
disableDate: "",
enableDate: "",
-
enableTime: "",
+ weekNumbers: "",
+
+ options: "",
disabled: "",
required: "",
- weekNumbers: "",
onChange: "&",
onClose: "&",
diff --git a/packages/oui-calendar/src/calendar.controller.js b/packages/oui-calendar/src/calendar.controller.js
index 794485bb..2d8b516b 100644
--- a/packages/oui-calendar/src/calendar.controller.js
+++ b/packages/oui-calendar/src/calendar.controller.js
@@ -1,15 +1,17 @@
-import { addBooleanParameter } from "@ovh-ui/common/component-utils";
+import { addBooleanParameter, addDefaultParameter } from "@ovh-ui/common/component-utils";
import Flatpickr from "flatpickr";
+import merge from "lodash/merge";
export default class {
- constructor ($attrs, $element, $timeout, ouiCalendarConfiguration) {
+ constructor ($attrs, $element, $scope, $timeout, ouiCalendarConfiguration) {
"ngInject";
this.$attrs = $attrs;
this.$element = $element;
+ this.$id = $scope.$id;
this.$timeout = $timeout;
this.locale = ouiCalendarConfiguration.locale;
- this.options = angular.copy(ouiCalendarConfiguration.options);
+ this.config = angular.copy(ouiCalendarConfiguration.options);
}
setModelValue (value) {
@@ -19,7 +21,7 @@ export default class {
setEventHooks (hooks) {
// Add a callback for each events
hooks.forEach((hook) => {
- this.options[hook] = (selectedDates, dateStr) => {
+ this.config[hook] = (selectedDates, dateStr) => {
this.model = dateStr;
this.$timeout(this[hook]({ selectedDates, dateStr }));
};
@@ -28,11 +30,15 @@ export default class {
setOptionsProperty (property, value) {
if (angular.isDefined(value)) {
- this.options[property] = value;
+ this.config[property] = value;
}
}
initCalendarInstance () {
+ if (this.options) {
+ this.config = merge(this.config, this.options);
+ }
+
// Set options from attributes
this.setOptionsProperty("appendTo", this.appendTo);
this.setOptionsProperty("defaultDate", this.model);
@@ -50,7 +56,7 @@ export default class {
this.setOptionsProperty("dateFormat", this.format);
if (angular.isDefined(this.altFormat)) {
- this.setOptionsProperty("altInput", true);
+ this.setOptionsProperty("altInput", !this.disabled);
this.setOptionsProperty("altFormat", this.altFormat);
}
@@ -76,7 +82,7 @@ export default class {
});
// Init the flatpickr instance
- this.flatpickr = new Flatpickr(this.$element.find("input")[0], this.options);
+ this.flatpickr = new Flatpickr(this.$element.find("input")[0], this.config);
}
$onInit () {
@@ -84,10 +90,14 @@ export default class {
addBooleanParameter(this, "disabled");
addBooleanParameter(this, "enableTime");
addBooleanParameter(this, "inline");
+ addBooleanParameter(this, "noCalendar");
addBooleanParameter(this, "required");
addBooleanParameter(this, "static");
addBooleanParameter(this, "weekNumbers");
+ addDefaultParameter(this, "id", `ouiCalendar${this.$id}`);
+ addDefaultParameter(this, "name", `ouiCalendar${this.$id}`);
+
this.initCalendarInstance();
}
@@ -96,16 +106,20 @@ export default class {
}
$postLink () {
- // Avoid $element DOM unsync for jqLite methods
this.$timeout(() => {
+ const controls = angular.element(this.$element[0].querySelectorAll(".oui-calendar__control"));
+
this.$element
.addClass("oui-calendar")
.removeAttr("id")
.removeAttr("name");
- // Add class for `inline`
+ // Avoid 'alt-input' to take bad value of placeholder
+ controls.attr("placeholder", this.placeholder);
+
if (this.inline) {
this.$element.addClass("oui-calendar_inline");
+ controls.attr("type", "hidden");
}
});
}
diff --git a/packages/oui-calendar/src/calendar.html b/packages/oui-calendar/src/calendar.html
index a422784f..67b32f10 100644
--- a/packages/oui-calendar/src/calendar.html
+++ b/packages/oui-calendar/src/calendar.html
@@ -3,20 +3,7 @@
autocomplete="off"
ng-attr-id="{{::$ctrl.id}}"
ng-attr-name="{{::$ctrl.name}}"
- ng-attr-placeholder="{{::$ctrl.placeholder}}"
ng-model="$ctrl.model"
ng-disabled="$ctrl.disabled"
ng-required="$ctrl.required" />
-
diff --git a/packages/oui-calendar/src/index.spec.js b/packages/oui-calendar/src/index.spec.js
index e35fedf1..d0bf51a4 100644
--- a/packages/oui-calendar/src/index.spec.js
+++ b/packages/oui-calendar/src/index.spec.js
@@ -65,7 +65,7 @@ describe("ouiCalendar", () => {
$timeout.flush();
- expect(controller.options.inline).toBe(true);
+ expect(controller.config.inline).toBe(true);
expect(component.hasClass("oui-calendar_inline")).toBe(true);
});
@@ -76,7 +76,7 @@ describe("ouiCalendar", () => {
$timeout.flush();
- expect(controller.options.appendTo).toBeUndefined();
+ expect(controller.config.appendTo).toBeUndefined();
expect(calendar).toBeNull();
});
@@ -98,6 +98,8 @@ describe("ouiCalendar", () => {
const component = testUtils.compileTemplate('');
const input = component.find("input");
+ $timeout.flush();
+
expect(input.attr("placeholder")).toBe("foo");
});
@@ -106,7 +108,7 @@ describe("ouiCalendar", () => {
const ctrl = component.controller("ouiCalendar");
ctrl.setOptionsProperty("foo", "bar");
- expect(ctrl.options.foo).toBe("bar");
+ expect(ctrl.config.foo).toBe("bar");
});
it("should change the value formatting of the model and the input", () => {
@@ -124,9 +126,9 @@ describe("ouiCalendar", () => {
const input = component[0].querySelector(".oui-calendar__control");
const altInput = component[0].querySelector(".oui-calendar__control_alt");
- expect(ctrl.options.dateFormat).toBe(format);
- expect(ctrl.options.altInput).toBe(true);
- expect(ctrl.options.altFormat).toBe(altFormat);
+ expect(ctrl.config.dateFormat).toBe(format);
+ expect(ctrl.config.altInput).toBe(true);
+ expect(ctrl.config.altFormat).toBe(altFormat);
expect(input.value).toBe(formatDate);
expect(altInput.value).toBe(altFormatDate);
});
@@ -136,7 +138,7 @@ describe("ouiCalendar", () => {
const ctrl = component.controller("ouiCalendar");
ctrl.setEventHooks(["foo"]);
- expect(typeof ctrl.options.foo).toBe("function");
+ expect(typeof ctrl.config.foo).toBe("function");
});
it("should call function of events attributes", () => {
@@ -153,7 +155,7 @@ describe("ouiCalendar", () => {
onOpenSpy
});
const ctrl = component.controller("ouiCalendar");
- const today = ctrl.flatpickr.parseDate("today", ctrl.options.dateFormat);
+ const today = ctrl.flatpickr.parseDate("today", ctrl.config.dateFormat);
ctrl.setModelValue(today);
expect(onChangeSpy).toHaveBeenCalledWith([today], ctrl.model);
@@ -162,26 +164,5 @@ describe("ouiCalendar", () => {
ctrl.flatpickr.close();
expect(onCloseSpy).toHaveBeenCalledWith([today], ctrl.model);
});
-
- // it("should set the value to today's date when 'today' button is clicked", () => {
- // const component = testUtils.compileTemplate('');
- // const ctrl = component.controller("ouiCalendar");
- // const button = component.find("button").eq(0);
- // const today = ctrl.flatpickr.formatDate(new Date(), ctrl.options.dateFormat);
-
- // button.triggerHandler("click");
- // expect(ctrl.model).toBe(today);
- // });
-
- // it("should reset the value when 'reset' button is clicked", () => {
- // const component = testUtils.compileTemplate('', {
- // model: "today"
- // });
- // const ctrl = component.controller("ouiCalendar");
- // const button = component.find("button").eq(1);
-
- // button.triggerHandler("click");
- // expect(ctrl.model).toBe("");
- // });
});
});
diff --git a/packages/oui-timepicker/README.md b/packages/oui-timepicker/README.md
new file mode 100644
index 00000000..d24d0896
--- /dev/null
+++ b/packages/oui-timepicker/README.md
@@ -0,0 +1,115 @@
+# Timepicker
+
+
+
+## Usage
+
+### Basic
+
+```html:preview
+
+```
+
+### Placeholder
+
+```html:preview
+
+```
+
+### Disabled
+
+```html:preview
+
+```
+
+### Enabling seconds
+
+Use `enable-seconds` to show seconds selector.
+
+```html:preview
+
+```
+
+### Enabling AM/PM
+
+Use `enable-am-pm` to show AM/PM selector.
+
+```html:preview
+
+```
+
+### Time formatting
+
+
+ See Formatting Tokens for more information.
+
+
+```html:preview
+
+
+
+
Model value: {{$ctrl.formatModel | json}}
+
+```
+
+### Inline
+
+```html:preview
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Events
+
+
+ If you want to access the model inside callbacks, you need to use the modelValue variable as below.
+
+
+```html:preview
+
+
+
+
Model value: {{$ctrl.eventsModel | json}}
+
onChange values: {{$ctrl.onChangeValue | json}}
+
onOpen values: {{$ctrl.onOpenValue | json}}
+
onClose values: {{$ctrl.onCloseValue | json}}
+
+```
+
+## API
+
+| Attribute | Type | Binding | One-time Binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `model` | object | = | no | See [Supplying Dates](https://flatpickr.js.org/examples/#supplying-dates-for-flatpickr) | n/a | model bound to component
+| `id` | string | @? | yes | n/a | n/a | id attribute of the field
+| `name` | string | @? | yes | n/a | n/a | name attribute of the field
+| `placeholder` | string | @? | yes | n/a | n/a | placeholder text
+| `format` | string | @? | yes | See [Formatting Tokens](https://flatpickr.js.org/formatting/) | `H:i` | format the date of the model
+| `alt-format` | string | @? | yes | See [Formatting Tokens](https://flatpickr.js.org/formatting/) | `H:i` | format the date of the field. `format` is used if undefined
+| `append-to-body` | boolean | | yes | `true`, `false` | `false` | append the timepicker to the body of the page
+| `inline` | boolean | | no | `true`, `false` | `false` | show the timepicker below the input
+| `static` | boolean | | no | `true`, `false` | `false` | position the timepicker relatively to the input
+| `enable-seconds` | boolean | | yes | `true`, `false` | `false` | enables seconds selection
+| `enable-am-pm` | boolean | | yes | `true`, `false` | `false` | enables am/pm selection
+| `disabled` | boolean | | no | `true`, `false` | `false` | disabled flag
+| `required` | boolean | | no | `true`, `false` | `false` | required flag
+| `on-change` | function | & | no | n/a | n/a | handler triggered when the user change the selected time
+| `on-close` | function | & | no | n/a | n/a | handler triggered when the timepicker is closed
+| `on-open` | function | & | no | n/a | n/a | handler triggered when the timepicker is opened
diff --git a/packages/oui-timepicker/package.json b/packages/oui-timepicker/package.json
new file mode 100644
index 00000000..303f6da6
--- /dev/null
+++ b/packages/oui-timepicker/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@ovh-ui/oui-timepicker",
+ "version": "1.0.0",
+ "main": "./src/index.js",
+ "license": "BSD-3-Clause",
+ "author": "OVH SAS",
+ "dependencies": {
+ "@ovh-ui/oui-calendar": "^1.0.0"
+ }
+}
diff --git a/packages/oui-timepicker/src/index.js b/packages/oui-timepicker/src/index.js
new file mode 100644
index 00000000..894914fc
--- /dev/null
+++ b/packages/oui-timepicker/src/index.js
@@ -0,0 +1,7 @@
+import Calendar from "@ovh-ui/oui-calendar";
+import Timepicker from "./timepicker.component";
+
+export default angular
+ .module("oui.timepicker", [Calendar])
+ .component("ouiTimepicker", Timepicker)
+ .name;
diff --git a/packages/oui-timepicker/src/index.spec.js b/packages/oui-timepicker/src/index.spec.js
new file mode 100644
index 00000000..221fed93
--- /dev/null
+++ b/packages/oui-timepicker/src/index.spec.js
@@ -0,0 +1,91 @@
+describe("ouiCalendar", () => {
+ let $timeout;
+ let testUtils;
+
+ beforeEach(angular.mock.module("oui.timepicker"));
+ beforeEach(angular.mock.module("oui.test-utils"));
+
+ beforeEach(inject((_$timeout_, _TestUtils_) => {
+ $timeout = _$timeout_;
+ testUtils = _TestUtils_;
+ }));
+
+ describe("Component", () => {
+ it("should add the classname .oui-timepicker on the root element", () => {
+ const component = testUtils.compileTemplate('');
+
+ $timeout.flush();
+
+ expect(component.hasClass("oui-timepicker")).toBe(true);
+ });
+
+ it("should have an attribute id and name on the input, and removed on the root component", () => {
+ const component = testUtils.compileTemplate('');
+ const input = component.find("input");
+
+ $timeout.flush();
+
+ expect(component.attr("id")).toBe(undefined);
+ expect(input.attr("id")).toBe("foo");
+
+ expect(component.attr("name")).toBe(undefined);
+ expect(input.attr("name")).toBe("bar");
+ });
+
+ it("should set the picker inline", () => {
+ const component = testUtils.compileTemplate('');
+ const controller = component.controller("ouiTimepicker");
+
+ $timeout.flush();
+
+ expect(controller.inline).toBe(true);
+ expect(component.hasClass("oui-timepicker_inline")).toBe(true);
+ });
+
+ it("should append the picker to the body", () => {
+ const component = testUtils.compileTemplate('');
+ const picker = component[0].querySelector(".flatpickr-calendar");
+
+ $timeout.flush();
+
+ expect(picker).toBeNull();
+ });
+
+ it("should have disabled the input", () => {
+ const component = testUtils.compileTemplate('');
+ const input = component.find("input");
+
+ expect(input.attr("disabled")).toBe("disabled");
+ });
+
+ it("should have required the input", () => {
+ const component = testUtils.compileTemplate('');
+ const input = component.find("input");
+
+ expect(input.attr("required")).toBe("required");
+ });
+
+ it("should have a placeholder on the input", () => {
+ const component = testUtils.compileTemplate('');
+ const input = component.find("input");
+
+ $timeout.flush();
+
+ expect(input.attr("placeholder")).toBe("foo");
+ });
+
+ it("should set 'enableSeconds' to true", () => {
+ const component = testUtils.compileTemplate('');
+ const controller = component.controller("ouiTimepicker");
+
+ expect(controller.options.enableSeconds).toBe(true);
+ });
+
+ it("should set 'time_24hr' to false", () => {
+ const component = testUtils.compileTemplate('');
+ const controller = component.controller("ouiTimepicker");
+
+ expect(controller.options.time_24hr).toBe(false);
+ });
+ });
+});
diff --git a/packages/oui-timepicker/src/timepicker.component.js b/packages/oui-timepicker/src/timepicker.component.js
new file mode 100644
index 00000000..20e37b66
--- /dev/null
+++ b/packages/oui-timepicker/src/timepicker.component.js
@@ -0,0 +1,29 @@
+import controller from "./timepicker.controller";
+import template from "./timepicker.html";
+
+export default {
+ bindings: {
+ model: "=",
+
+ id: "@?",
+ name: "@?",
+ placeholder: "@?",
+ format: "@?",
+ altFormat: "@?",
+
+ appendToBody: "",
+ inline: "",
+ "static": "",
+ enableSeconds: "",
+ enableAmPm: "",
+
+ disabled: "",
+ required: "",
+
+ onChange: "&",
+ onClose: "&",
+ onOpen: "&"
+ },
+ controller,
+ template
+};
diff --git a/packages/oui-timepicker/src/timepicker.controller.js b/packages/oui-timepicker/src/timepicker.controller.js
new file mode 100644
index 00000000..245383d5
--- /dev/null
+++ b/packages/oui-timepicker/src/timepicker.controller.js
@@ -0,0 +1,61 @@
+import { addBooleanParameter, addDefaultParameter } from "@ovh-ui/common/component-utils";
+
+export default class {
+ constructor ($attrs, $element, $timeout) {
+ "ngInject";
+
+ this.$attrs = $attrs;
+ this.$element = $element;
+ this.$timeout = $timeout;
+
+ this.options = {
+ enableTime: true,
+ enableSeconds: false,
+ noCalendar: true,
+ dateFormat: "H:i",
+ time_24hr: true
+ };
+ }
+
+ setOptionsProperty (property, value) {
+ if (angular.isDefined(value)) {
+ this.options[property] = value;
+ }
+ }
+
+ $onInit () {
+ addDefaultParameter(this, "id", `ouiTimepicker${this.$id}`);
+ addDefaultParameter(this, "name", `ouiTimepicker${this.$id}`);
+ addDefaultParameter(this, "format", this.options.dateFormat);
+ addDefaultParameter(this, "altFormat", this.format || this.options.dateFormat);
+
+ addBooleanParameter(this, "appendToBody");
+ addBooleanParameter(this, "disabled");
+ addBooleanParameter(this, "enableSeconds");
+ addBooleanParameter(this, "enableAmPm");
+ addBooleanParameter(this, "inline");
+ addBooleanParameter(this, "required");
+ addBooleanParameter(this, "static");
+
+ this.setOptionsProperty("enableSeconds", this.enableSeconds);
+ this.setOptionsProperty("time_24hr", !this.enableAmPm);
+
+ // flatpickr's timepicker need a default value to work in inline mode
+ if (this.inline) {
+ this.setOptionsProperty("defaultDate", this.model || "today");
+ }
+ }
+
+ $postLink () {
+ this.$timeout(() => {
+ this.$element
+ .addClass("oui-timepicker")
+ .removeAttr("id")
+ .removeAttr("name");
+
+ if (this.inline) {
+ this.$element.addClass("oui-timepicker_inline");
+ }
+ });
+ }
+}
diff --git a/packages/oui-timepicker/src/timepicker.html b/packages/oui-timepicker/src/timepicker.html
new file mode 100644
index 00000000..391f6161
--- /dev/null
+++ b/packages/oui-timepicker/src/timepicker.html
@@ -0,0 +1,16 @@
+
+
diff --git a/packages/oui-timepicker/tests/index.js b/packages/oui-timepicker/tests/index.js
new file mode 100644
index 00000000..ebd31bdb
--- /dev/null
+++ b/packages/oui-timepicker/tests/index.js
@@ -0,0 +1,7 @@
+import "@ovh-ui/common/test-utils";
+
+loadTests(require.context("../src/", true, /.*((\.spec)|(index))$/));
+
+function loadTests (context) {
+ context.keys().forEach(context);
+}